@event4u/agent-config 16.0.0 → 16.1.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +297 -137
- package/CONTRIBUTING.md +9 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +26 -0
- package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
- package/dist/agent-src/commands/challenge-me/closure.md +178 -0
- package/dist/agent-src/commands/challenge-me.md +9 -2
- package/dist/agent-src/commands/council/default.md +25 -5
- package/dist/agent-src/commands/feature/plan.md +28 -1
- package/dist/agent-src/commands/feature/roadmap.md +28 -1
- package/dist/agent-src/commands/implement-ticket.md +27 -0
- package/dist/agent-src/commands/jira-ticket.md +27 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/pr/merge.md +60 -9
- package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
- package/dist/agent-src/commands/roadmap/create.md +74 -27
- package/dist/agent-src/commands/roadmap/materialize.md +27 -0
- package/dist/agent-src/commands/roadmap/process-full.md +123 -18
- package/dist/agent-src/commands/team.md +22 -0
- package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
- package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
- package/dist/agent-src/rules/autonomous-execution.md +15 -8
- package/dist/agent-src/rules/delegation-policy.md +23 -45
- package/dist/agent-src/rules/design-fidelity.md +32 -37
- package/dist/agent-src/rules/evaluator-independence.md +43 -49
- package/dist/agent-src/rules/test-first.md +41 -0
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
- package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
- package/dist/agent-src/skills/database/data/manifest.json +4 -4
- package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
- package/dist/agent-src/skills/design-review/SKILL.md +50 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
- package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +188 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/dist/cli-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
- package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
- package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
- package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
- package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
- package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
- package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
- package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
- package/dist/cli-delegate/chunk-YDPILCOL.js +152 -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 +90 -56
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +9 -7
- package/dist/cli-delegate/cmd_fleet.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- 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_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_get.js +28 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +10 -8
- package/dist/cli-delegate/cmd_uninstall.js +16 -13
- 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 +12 -10
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +85 -38
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +1832 -1007
- package/dist/install/claudeRuleRewrite.js +14 -3
- package/dist/install/conflict.js +60 -10
- package/dist/install/conflictTracker.js +218 -0
- package/dist/install/fsPrimitives.js +54 -0
- package/dist/install/install.mjs +2007 -1481
- package/dist/install/manifestFiles.js +259 -0
- package/dist/install/preflight.js +14 -2
- package/dist/install/preserve.js +157 -0
- package/dist/install/recordedOwnership.js +132 -0
- package/dist/install/txlog.js +11 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +398 -39
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +3 -2
- package/dist/scripts/_lib/fs_atomic.js +167 -0
- package/dist/scripts/_lib/installed_tools.js +460 -0
- package/dist/scripts/_lib/lean_projection_mode.js +3 -2
- package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +17 -5
- package/dist/shared/missionExecution.js +97 -0
- package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +26 -7
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/docs/guidelines/design-fidelity-routing.md +29 -0
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/src/config/agent-settings.template.yml +139 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +18 -5
- package/src/config/continuity-surface.json +21 -0
- package/src/config/gate-coverage.yml +146 -13
- package/src/config/gate-violation-baselines.json +6 -6
- package/src/config/hook-token-budget.json +2 -0
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/install/claudeRuleRewrite.ts +16 -2
- package/src/install/conflict.ts +65 -9
- package/src/install/conflictTracker.ts +299 -0
- package/src/install/fsPrimitives.ts +54 -0
- package/src/install/manifestFiles.ts +312 -0
- package/src/install/preflight.ts +16 -2
- package/src/install/preserve.ts +205 -0
- package/src/install/recordedOwnership.ts +149 -0
- package/src/install/txlog.ts +11 -2
- package/src/install/types.ts +19 -4
- package/src/scripts/_cli/cmd_conformance.ts +88 -15
- package/src/scripts/_cli/cmd_doctor.ts +4 -3
- package/src/scripts/_cli/cmd_route_explain.ts +9 -2
- package/src/scripts/_cli/cmd_settings_get.ts +44 -0
- package/src/scripts/_cli/doctor_execution.ts +90 -0
- package/src/scripts/_lib/activation_payload.ts +247 -0
- package/src/scripts/_lib/agent_settings.ts +4 -2
- package/src/scripts/_lib/authority_path.ts +102 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- package/src/scripts/_lib/continuation_ladder.ts +149 -2
- package/src/scripts/_lib/council_transport.ts +129 -0
- package/src/scripts/_lib/delivery_ready.ts +112 -0
- package/src/scripts/_lib/forge_protection.ts +267 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/mission_record.ts +190 -0
- package/src/scripts/_lib/obligation_frequency.ts +217 -0
- package/src/scripts/_lib/obligations.ts +402 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/release_highlights.ts +199 -10
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -0
- package/src/scripts/ai_council/config.ts +8 -11
- package/src/scripts/ai_council/fallback_config.ts +5 -3
- package/src/scripts/ai_council/ownership_classes.ts +118 -0
- package/src/scripts/ask_block_census.ts +285 -15
- package/src/scripts/check_enforcement_matrix.ts +624 -0
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +954 -0
- package/src/scripts/check_preamble_payload_budget.ts +75 -2
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +567 -0
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/closure_scan.ts +333 -0
- package/src/scripts/compile_hook_manifest.ts +43 -3
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +88 -9
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/design_pass_hook.ts +128 -3
- package/src/scripts/hooks/evidence_independence.ts +96 -0
- package/src/scripts/hooks/memory_recall_hook.ts +540 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
- package/src/scripts/hooks/rule_inject_hook.ts +49 -1
- package/src/scripts/hooks/run_continuation_hook.ts +17 -20
- package/src/scripts/hooks_doctor.ts +85 -0
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +106 -206
- package/src/scripts/janitor.ts +7 -0
- package/src/scripts/lint_decision_classes.ts +464 -0
- package/src/scripts/lint_hook_manifest.ts +11 -0
- package/src/scripts/lint_major_migration_sections.ts +450 -0
- package/src/scripts/lint_plan_risk_register.ts +47 -3
- package/src/scripts/lint_roadmap_blockers.ts +61 -0
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/release.ts +14 -98
- package/src/scripts/release_drill.ts +267 -1
- package/src/scripts/release_publication.ts +51 -0
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +4 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/trigger_eval_grandfather.json +0 -1
- package/src/scripts/ui_conformance_probe.ts +622 -0
- package/src/server/schemas/settings.ts +31 -7
- package/src/shared/missionExecution.ts +126 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recorded-ownership classification — road-to-a-conformance-check-that-can-fail
|
|
3
|
+
* Phase 5.
|
|
4
|
+
*
|
|
5
|
+
* The install conflict matrix decided ownership by **path-set membership**:
|
|
6
|
+
* `policy.knownPaths.has(targetPath)`. That answers "did we ever write here",
|
|
7
|
+
* which collapses two materially different trees into one row — a managed file
|
|
8
|
+
* still carrying the bytes we wrote, and a managed file the user has since
|
|
9
|
+
* edited. The second is a data-loss surface and the first is not, and the
|
|
10
|
+
* matrix could not tell them apart because a path set carries no content.
|
|
11
|
+
*
|
|
12
|
+
* This module is the plumbing half: read the per-file SHA-256 the install
|
|
13
|
+
* manifest already records, and compare it against a digest the caller
|
|
14
|
+
* computed. It changes no install behaviour on its own — the matrix wiring
|
|
15
|
+
* lands separately (Phase 5.2), so the behaviour change is reviewable as a
|
|
16
|
+
* diff that contains no hash computation.
|
|
17
|
+
*
|
|
18
|
+
* Why a local YAML read rather than `installed_tools.read_manifest()`: that
|
|
19
|
+
* reader is a hand-rolled parser with no YAML dependency, and it drops nested
|
|
20
|
+
* `files[]` entries by design (a degraded read that ADR-200 documents as
|
|
21
|
+
* intentional). It therefore cannot supply a recorded hash at all. Reading
|
|
22
|
+
* the same file with the `yaml` package — already a runtime dependency — is
|
|
23
|
+
* additive and leaves that reader's byte-exact golden tests untouched.
|
|
24
|
+
*
|
|
25
|
+
* `src/scripts/_lib/install_drift.ts` reads the same field of the same file
|
|
26
|
+
* for the same reason, and this module is a second reader rather than a
|
|
27
|
+
* caller of it because `collect_drift` computes the digests itself and
|
|
28
|
+
* returns only the entries that drifted. The conflict matrix needs the
|
|
29
|
+
* opposite: every recorded digest, and no hashing, because the digest it
|
|
30
|
+
* compares against is one the caller already has. What this module does NOT
|
|
31
|
+
* duplicate is that module's stated invariant — `manifest_path()` stays the
|
|
32
|
+
* one place that decides where the manifest is, so the
|
|
33
|
+
* `AGENT_CONFIG_INSTALLED_TOOLS` override and `~` expansion behave
|
|
34
|
+
* identically in both.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
38
|
+
import { homedir } from 'node:os';
|
|
39
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
40
|
+
|
|
41
|
+
import * as YAML from 'yaml';
|
|
42
|
+
|
|
43
|
+
import { manifest_path, type EnvMap } from '../scripts/_lib/installed_tools.js';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* How the install matrix owns a target on disk.
|
|
47
|
+
*
|
|
48
|
+
* - `recorded-unchanged` — the manifest records this path and the bytes on
|
|
49
|
+
* disk still match the digest it recorded. Ours, untouched.
|
|
50
|
+
* - `recorded-modified` — the manifest records this path and the bytes have
|
|
51
|
+
* changed since. Ours, edited by someone. Overwriting loses their work.
|
|
52
|
+
* - `unknown` — the manifest does not record this path, or records
|
|
53
|
+
* it without a digest (bridges carry `sha256: null`), or the bytes could not
|
|
54
|
+
* be read. Not an assertion that the file is foreign — an assertion that
|
|
55
|
+
* this tree cannot say.
|
|
56
|
+
*/
|
|
57
|
+
export type RecordedOwnership = 'recorded-unchanged' | 'recorded-modified' | 'unknown';
|
|
58
|
+
|
|
59
|
+
/** Absolute target path → the SHA-256 the manifest recorded (`null` for bridges). */
|
|
60
|
+
export type RecordedHashes = ReadonlyMap<string, string | null>;
|
|
61
|
+
|
|
62
|
+
/** The empty answer — "nothing is recorded", used whenever no manifest resolves. */
|
|
63
|
+
export const NO_RECORDED_HASHES: RecordedHashes = new Map<string, string | null>();
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Classify one target from the recorded digest and the on-disk digest.
|
|
67
|
+
*
|
|
68
|
+
* Pure — the caller supplies both digests. `undefined` for `recordedSha256`
|
|
69
|
+
* means the path is not recorded at all; `null` means recorded without a
|
|
70
|
+
* digest. Both answer `unknown`, because neither supports a comparison.
|
|
71
|
+
*/
|
|
72
|
+
export function classifyOwnership(
|
|
73
|
+
recordedSha256: string | null | undefined,
|
|
74
|
+
onDiskSha256: string | null,
|
|
75
|
+
): RecordedOwnership {
|
|
76
|
+
if (recordedSha256 === undefined || recordedSha256 === null) return 'unknown';
|
|
77
|
+
if (onDiskSha256 === null) return 'unknown';
|
|
78
|
+
return recordedSha256 === onDiskSha256 ? 'recorded-unchanged' : 'recorded-modified';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Read `tools[].files[]` out of an installed-tools manifest into a
|
|
83
|
+
* path → digest map.
|
|
84
|
+
*
|
|
85
|
+
* Relative paths resolve against `projectRoot` and a leading `~` expands
|
|
86
|
+
* first — the manifest stores user-scope entries absolute or `~`-rooted and
|
|
87
|
+
* project-scope entries relative, and both existing readers
|
|
88
|
+
* (`install_drift.resolve_entry_path`, `cmd_doctor._resolve_path`) resolve
|
|
89
|
+
* them that way. A missing, unreadable or malformed manifest yields the
|
|
90
|
+
* empty map rather than throwing — an install plan must never fail because a
|
|
91
|
+
* lockfile is corrupt.
|
|
92
|
+
*/
|
|
93
|
+
export function readRecordedHashes(manifestPath: string, projectRoot: string): RecordedHashes {
|
|
94
|
+
if (!existsSync(manifestPath)) return NO_RECORDED_HASHES;
|
|
95
|
+
let doc: unknown;
|
|
96
|
+
try {
|
|
97
|
+
doc = YAML.parse(readFileSync(manifestPath, 'utf8'));
|
|
98
|
+
} catch {
|
|
99
|
+
return NO_RECORDED_HASHES;
|
|
100
|
+
}
|
|
101
|
+
if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) return NO_RECORDED_HASHES;
|
|
102
|
+
const tools = (doc as Record<string, unknown>)['tools'];
|
|
103
|
+
if (!Array.isArray(tools)) return NO_RECORDED_HASHES;
|
|
104
|
+
|
|
105
|
+
const out = new Map<string, string | null>();
|
|
106
|
+
for (const tool of tools) {
|
|
107
|
+
if (tool === null || typeof tool !== 'object' || Array.isArray(tool)) continue;
|
|
108
|
+
const files = (tool as Record<string, unknown>)['files'];
|
|
109
|
+
if (!Array.isArray(files)) continue;
|
|
110
|
+
for (const entry of files) {
|
|
111
|
+
if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) continue;
|
|
112
|
+
const rec = entry as Record<string, unknown>;
|
|
113
|
+
const raw = rec['path'];
|
|
114
|
+
if (typeof raw !== 'string' || raw.length === 0) continue;
|
|
115
|
+
const sha = rec['sha256'];
|
|
116
|
+
const expanded = expanduser(raw);
|
|
117
|
+
const target = isAbsolute(expanded)
|
|
118
|
+
? resolve(expanded)
|
|
119
|
+
: resolve(projectRoot, expanded);
|
|
120
|
+
out.set(target, typeof sha === 'string' && sha.length > 0 ? sha : null);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Expand a leading `~`, matching `installed_tools.expanduser`. */
|
|
127
|
+
function expanduser(p: string): string {
|
|
128
|
+
if (p === '~') return homedir();
|
|
129
|
+
if (p.startsWith('~/') || (process.platform === 'win32' && p.startsWith('~\\'))) {
|
|
130
|
+
return join(homedir(), p.slice(2));
|
|
131
|
+
}
|
|
132
|
+
return p;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* {@link readRecordedHashes} for a PROJECT root, via `installed_tools`.
|
|
137
|
+
*
|
|
138
|
+
* `manifest_path` carries the `AGENT_CONFIG_INSTALLED_TOOLS` override, so an
|
|
139
|
+
* install running against a relocated manifest classifies ownership instead
|
|
140
|
+
* of degrading every file to `unknown`.
|
|
141
|
+
*
|
|
142
|
+
* The argument must be a project root. An {@link InstallPlan.root} for a
|
|
143
|
+
* `global` target is the install anchor (`~/.event4u/agent-config/`), which
|
|
144
|
+
* holds no manifest — such a plan gets the empty map and therefore the
|
|
145
|
+
* pre-hash answer, which is correct but is a floor rather than a feature.
|
|
146
|
+
*/
|
|
147
|
+
export function recordedHashesForRoot(root: string, env?: EnvMap | null): RecordedHashes {
|
|
148
|
+
return readRecordedHashes(manifest_path(root, env), root);
|
|
149
|
+
}
|
package/src/install/txlog.ts
CHANGED
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
* Transaction log — Phase A4 (council Finding #18).
|
|
3
3
|
*
|
|
4
4
|
* Append-only JSONL at `getLogPath()` (`~/.event4u/agent-config/install-log.jsonl`).
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Recovery on crash is `tail -n` plus the wizard's resume / dismiss decision —
|
|
6
|
+
* nothing is un-written automatically. No PID lockfile (per Phase-A4
|
|
7
|
+
* decision 8).
|
|
8
|
+
*
|
|
9
|
+
* WHAT ACTUALLY WRITES THIS FILE, as of 2026-09-15: two call sites.
|
|
10
|
+
* `src/server/routes/install.ts`'s recovery-dismiss handler appends a
|
|
11
|
+
* `rollback` marker with an empty path and a null hash; `src/scripts/install.ts`
|
|
12
|
+
* — the surviving headless writer, per-file, through `_log_tx_entry` — appends
|
|
13
|
+
* a `write` or `skip` entry per copied/skipped path during
|
|
14
|
+
* `_copy_dir_dereferencing_symlinks`. Both call `appendTxLog` directly, so the
|
|
15
|
+
* entry shape is identical by construction rather than by convention.
|
|
7
16
|
*
|
|
8
17
|
* Rotation: 10 MB OR 30 days, whichever first. Rotated copies move to
|
|
9
18
|
* `install-log.<ISO>.jsonl.gz` siblings; recovery never scans them.
|
package/src/install/types.ts
CHANGED
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
* - v2 manifest `files_by_tool` → {@link InstallPlan.filesByTool}
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
// Type-only — erased at compile time, so this module stays runtime-free.
|
|
20
|
+
import type { RecordedOwnership } from './recordedOwnership.js';
|
|
21
|
+
|
|
19
22
|
/**
|
|
20
23
|
* Where the install lands.
|
|
21
24
|
*
|
|
@@ -130,9 +133,11 @@ export interface InstallPlan {
|
|
|
130
133
|
* Phase B3 — surfaced by the `/api/v1/install/plan` route so the wizard's
|
|
131
134
|
* conflict screen can render single-pick / batch-resolution UI **before**
|
|
132
135
|
* the apply phase opens the transaction log. A `ConflictEntry` means the
|
|
133
|
-
* target exists, its bytes do not match the planned SHA,
|
|
134
|
-
*
|
|
135
|
-
* would surface this file to the UI
|
|
136
|
+
* target exists, its bytes do not match the planned SHA, `policy.force` is
|
|
137
|
+
* false, and the planner does not hold it as ours-and-unchanged — i.e. the
|
|
138
|
+
* policy would surface this file to the UI. Since `ownership` landed it is
|
|
139
|
+
* NO LONGER equivalent to "not in `policy.knownPaths`": a `recorded-modified`
|
|
140
|
+
* entry is emitted precisely when the path IS in that set.
|
|
136
141
|
*
|
|
137
142
|
* `mergeable` is `true` only for `.json` deployed files; the wizard
|
|
138
143
|
* shows the per-row `merge` button only on those. `existingSha256` is
|
|
@@ -145,6 +150,15 @@ export interface ConflictEntry {
|
|
|
145
150
|
readonly plannedSha256: string | null;
|
|
146
151
|
readonly existingSha256: string | null;
|
|
147
152
|
readonly mergeable: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Three-state ownership from the manifest's recorded digest — the column
|
|
155
|
+
* path-set membership could not produce (Phase 5.1 of
|
|
156
|
+
* road-to-a-conformance-check-that-can-fail). `recorded-modified` is the
|
|
157
|
+
* row that matters: a file we wrote that someone has since edited.
|
|
158
|
+
* `unknown` reproduces the pre-hash answer and is what every tree without
|
|
159
|
+
* a readable manifest reports.
|
|
160
|
+
*/
|
|
161
|
+
readonly ownership: RecordedOwnership;
|
|
148
162
|
}
|
|
149
163
|
|
|
150
164
|
/**
|
|
@@ -164,7 +178,8 @@ export type ConflictResolution = 'skip' | 'overwrite' | 'merge';
|
|
|
164
178
|
* Result of applying an {@link InstallPlan}.
|
|
165
179
|
*
|
|
166
180
|
* Surfaced to the wizard's progress bar (Phase B1) and recorded in the
|
|
167
|
-
* transaction log (Phase A4) so
|
|
181
|
+
* transaction log (Phase A4) so a crashed run's tail can be surfaced to
|
|
182
|
+
* the wizard's recovery screen.
|
|
168
183
|
*/
|
|
169
184
|
export interface ApplyResult {
|
|
170
185
|
readonly target: InstallTarget;
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* consumer-facing checks that answer ONE question deterministically:
|
|
7
7
|
* "is agent-config installed AND firing in this repo?"
|
|
8
8
|
*
|
|
9
|
-
* (a) txlog-clean — install log tail carries no abandoned run
|
|
9
|
+
* (a) txlog-clean — install log tail carries no abandoned run;
|
|
10
|
+
* an absent log on an installed tree is `unknown`,
|
|
11
|
+
* not green (see `_check_txlog_clean`)
|
|
10
12
|
* (b) router-pointers — dist/router.json ids + routes_to resolve on disk
|
|
11
13
|
* (c) hook-dispatcher — dispatcher answers synthetic session_start/stop
|
|
12
14
|
* (d) lean-projection — lean_projection.mode matches projected artifacts
|
|
@@ -75,6 +77,21 @@ export const CONFORMANCE_CHECK_IDS = [
|
|
|
75
77
|
/** Marker emitted by `project_thin_rules.thin_entry()` — the thin-stub signature. */
|
|
76
78
|
export const THIN_STUB_MARKER = 'Routed rule — load the body on trigger-match';
|
|
77
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Rendering symbols for this verb's rows — doctor's table plus `unknown`.
|
|
82
|
+
*
|
|
83
|
+
* `unknown` is a conformance concept: the check ran and could not answer.
|
|
84
|
+
* Distinct from `skipped` ("not applicable here") and from `ok` ("answered,
|
|
85
|
+
* green"), and never folded into the exit code, which keys off `fail` alone.
|
|
86
|
+
* It lives here rather than in `cmd_doctor`'s table because doctor emits no
|
|
87
|
+
* such row — and because `cmd_doctor.ts` is already over the source-size
|
|
88
|
+
* ceiling, so a line added there is a ratchet violation.
|
|
89
|
+
*/
|
|
90
|
+
const CONFORMANCE_STATUS_SYMBOLS: Record<string, string> = {
|
|
91
|
+
...STATUS_SYMBOLS,
|
|
92
|
+
unknown: '❔',
|
|
93
|
+
};
|
|
94
|
+
|
|
78
95
|
function print(s = ''): void {
|
|
79
96
|
process.stdout.write(`${s}\n`);
|
|
80
97
|
}
|
|
@@ -105,21 +122,75 @@ export function installLogPath(): string {
|
|
|
105
122
|
// Check (a) — txlog tail clean.
|
|
106
123
|
// ---------------------------------------------------------------------------
|
|
107
124
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
/**
|
|
126
|
+
* True when this tree carries an install manifest — i.e. something was
|
|
127
|
+
* installed here, so an install transaction log was expected.
|
|
128
|
+
*
|
|
129
|
+
* The discriminator for the two absent-log cases below. A tree with no
|
|
130
|
+
* manifest was never installed and has genuinely nothing to recover; a
|
|
131
|
+
* tree WITH a manifest recorded an install and no log for it, which is
|
|
132
|
+
* not the same answer and must not be reported as the same colour.
|
|
133
|
+
*/
|
|
134
|
+
export function _installLogExpected(projectRoot: string): boolean {
|
|
135
|
+
return fs.existsSync(installed_tools.manifest_path(projectRoot));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check (a) — the install transaction log tail carries no abandoned run.
|
|
140
|
+
*
|
|
141
|
+
* A log with nothing usable in it is two answers, not one
|
|
142
|
+
* (road-to-a-conformance-check-that-can-fail Phase 1). Until this split the
|
|
143
|
+
* branch returned `ok` unconditionally, so every install satisfied the check
|
|
144
|
+
* by having produced nothing.
|
|
145
|
+
*
|
|
146
|
+
* - nothing usable, no manifest → `ok` (nothing was installed here)
|
|
147
|
+
* - nothing usable, manifest → `unknown` (an install recorded nothing)
|
|
148
|
+
* - usable entries → `ok` / `fail` on the tail, as before
|
|
149
|
+
*
|
|
150
|
+
* `unknown` is deliberately NOT `fail`: an install that predates the log, or
|
|
151
|
+
* one written by a path that does not log, is an unanswered question rather
|
|
152
|
+
* than a broken install, and reddening every such consumer at upgrade is the
|
|
153
|
+
* risk this phase's shape exists to avoid. The exit contract keys off `fail`
|
|
154
|
+
* only, so `unknown` leaves exit codes untouched — including the verdict
|
|
155
|
+
* banner, where the per-row symbol is the only signal.
|
|
156
|
+
*
|
|
157
|
+
* Reach, updated 2026-09-15 — Phase 3 landed: the headless installer
|
|
158
|
+
* (`src/scripts/install.ts`, `_copy_dir_dereferencing_symlinks`) now appends
|
|
159
|
+
* a `write` or `skip` entry per copied/skipped path, through the same
|
|
160
|
+
* `appendTxLog` module the recovery-dismiss route calls. So a headless
|
|
161
|
+
* install now leaves the `unknown` branch above and reads `ok` on its own
|
|
162
|
+
* evidence rather than on the absence of any. `unknown` is still the answer
|
|
163
|
+
* for an install that predates this change, or for the wizard's browser
|
|
164
|
+
* path, which does not call the headless copy function; `fail` is still
|
|
165
|
+
* unreachable because nothing writes an `abort` yet.
|
|
166
|
+
*/
|
|
167
|
+
export function _check_txlog_clean(
|
|
168
|
+
logPath: string = installLogPath(),
|
|
169
|
+
projectRoot: string | null = null,
|
|
170
|
+
): Dict {
|
|
171
|
+
const entries = fs.existsSync(logPath) ? readRecentEntries(logPath) : [];
|
|
118
172
|
if (entries.length === 0) {
|
|
173
|
+
// One branch for all three no-usable-entries shapes — absent, empty,
|
|
174
|
+
// and unreadable. Splitting them let a zero-byte or fully-corrupt log
|
|
175
|
+
// reach `ok` on a tree the absent-log branch would have called
|
|
176
|
+
// `unknown`: `readRecentEntries` drops malformed lines silently, so
|
|
177
|
+
// "the file exists" is not evidence that anything recorded anything.
|
|
178
|
+
if (projectRoot !== null && _installLogExpected(projectRoot)) {
|
|
179
|
+
return {
|
|
180
|
+
id: 'txlog-clean',
|
|
181
|
+
status: 'unknown',
|
|
182
|
+
message:
|
|
183
|
+
'install manifest present, no usable install transaction log — ' +
|
|
184
|
+
'nothing recorded this install, so the last run cannot be confirmed complete',
|
|
185
|
+
remedy:
|
|
186
|
+
'no install path writes this log today; ' +
|
|
187
|
+
'run `agent-config doctor` for the drift answer this check cannot give',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
119
190
|
return {
|
|
120
191
|
id: 'txlog-clean',
|
|
121
192
|
status: 'ok',
|
|
122
|
-
message: 'install transaction log
|
|
193
|
+
message: 'no usable install transaction log and no install manifest — nothing was installed here',
|
|
123
194
|
remedy: '',
|
|
124
195
|
};
|
|
125
196
|
}
|
|
@@ -131,7 +202,9 @@ export function _check_txlog_clean(logPath: string = installLogPath()): Dict {
|
|
|
131
202
|
message:
|
|
132
203
|
`install log tail is an abandoned run (abort at ${last.ts}` +
|
|
133
204
|
`${last.note ? `: ${last.note}` : ''})`,
|
|
134
|
-
remedy:
|
|
205
|
+
remedy:
|
|
206
|
+
're-run `agent-config init` — it re-applies the plan over the partial tail; ' +
|
|
207
|
+
"the aborted run's writes stay on disk and are replaced only where the plan covers them",
|
|
135
208
|
};
|
|
136
209
|
}
|
|
137
210
|
return {
|
|
@@ -492,7 +565,7 @@ export function runConformanceChecks(opts: ConformanceRunOptions = {}): Dict[] {
|
|
|
492
565
|
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
493
566
|
const packageRoot = opts.packageRoot ?? PACKAGE_ROOT;
|
|
494
567
|
const checks: Dict[] = [];
|
|
495
|
-
checks.push(_check_txlog_clean());
|
|
568
|
+
checks.push(_check_txlog_clean(installLogPath(), projectRoot));
|
|
496
569
|
checks.push(_check_router_pointers(packageRoot));
|
|
497
570
|
if (opts.skipDispatcher === true) {
|
|
498
571
|
checks.push({
|
|
@@ -653,7 +726,7 @@ function main(argv: string[] | null = null): number {
|
|
|
653
726
|
print(` 📍 project_root: ${project_root}`);
|
|
654
727
|
print('conformance:');
|
|
655
728
|
for (const c of allChecks) {
|
|
656
|
-
const sym =
|
|
729
|
+
const sym = CONFORMANCE_STATUS_SYMBOLS[c['status'] as string] ?? '?';
|
|
657
730
|
print(` ${sym} ${c['id']}: ${c['message']}`);
|
|
658
731
|
if (c['status'] !== 'ok' && c['remedy']) {
|
|
659
732
|
print(` fix: ${c['remedy']}`);
|
|
@@ -114,6 +114,7 @@ import {
|
|
|
114
114
|
resolve_project_root,
|
|
115
115
|
type TraceRecord,
|
|
116
116
|
} from '../_lib/agent_settings.js';
|
|
117
|
+
import { executionJson, forgeProtectionJson, UNREAD_FORGE } from './doctor_execution.js';
|
|
117
118
|
import * as ai_council_clients from '../ai_council/clients.js';
|
|
118
119
|
import * as ai_council_config from '../ai_council/config.js';
|
|
119
120
|
import {
|
|
@@ -3074,9 +3075,9 @@ function _emit_json(
|
|
|
3074
3075
|
foreign,
|
|
3075
3076
|
tag_drift,
|
|
3076
3077
|
};
|
|
3077
|
-
if (origin !== null)
|
|
3078
|
-
|
|
3079
|
-
|
|
3078
|
+
if (origin !== null) payload['project_root_origin'] = origin;
|
|
3079
|
+
payload['execution'] = executionJson(() => iter_setting_overrides({ cwd: project_root }));
|
|
3080
|
+
payload['forge_protection'] = forgeProtectionJson(UNREAD_FORGE); // 3.2
|
|
3080
3081
|
if (checks !== null) {
|
|
3081
3082
|
payload['checks'] = checks;
|
|
3082
3083
|
const drift = missing.length + modified.length + foreign.length + tag_drift.length;
|
|
@@ -78,7 +78,14 @@ function _trigger_label(trigger: Trigger): string {
|
|
|
78
78
|
return `trigger: ${JSON.stringify(trigger)}`;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
/**
|
|
81
|
+
/**
|
|
82
|
+
* Projection mode from settings.
|
|
83
|
+
*
|
|
84
|
+
* The shipped template carries `delivery` for `claude-code` (ADR-267), so on a
|
|
85
|
+
* readable settings file that is what comes back. The `eager-all` below is the
|
|
86
|
+
* parser fallback for an absent or unreadable value, matching
|
|
87
|
+
* `DEFAULT_LEAN_PROJECTION_MODE` — it is not what the template ships.
|
|
88
|
+
*/
|
|
82
89
|
export function _projection_mode(): string {
|
|
83
90
|
try {
|
|
84
91
|
const settings = load_agent_settings({ cwd: REPO_ROOT }) as Record<string, unknown>;
|
|
@@ -90,7 +97,7 @@ export function _projection_mode(): string {
|
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
} catch {
|
|
93
|
-
// settings unreadable →
|
|
100
|
+
// settings unreadable → parser fallback, not the template value
|
|
94
101
|
}
|
|
95
102
|
return 'eager-all';
|
|
96
103
|
}
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
* `2` usage error.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
+
import {
|
|
37
|
+
resolveLocalAutoRun,
|
|
38
|
+
type AutoRunResolution,
|
|
39
|
+
} from '../../shared/missionExecution.js';
|
|
36
40
|
import * as fs from 'node:fs';
|
|
37
41
|
import * as path from 'node:path';
|
|
38
42
|
import process from 'node:process';
|
|
@@ -142,6 +146,31 @@ export function carveOutFor(key: string): CarveOutKey | undefined {
|
|
|
142
146
|
return SETTINGS_CARVE_OUT.find((c) => c.key === key);
|
|
143
147
|
}
|
|
144
148
|
|
|
149
|
+
/**
|
|
150
|
+
* The mission-time resolution of `key`, or `null` when a mission changes nothing.
|
|
151
|
+
*
|
|
152
|
+
* Exactly one key resolves differently inside a mission today, and the function
|
|
153
|
+
* is keyed on that rather than generalised: a second mission-sensitive key would
|
|
154
|
+
* need its own argument about WHY the mission condition applies to it, and a
|
|
155
|
+
* generic table would let one land without that argument.
|
|
156
|
+
*
|
|
157
|
+
* `merged` is the already-loaded settings tree — no second cascade walk, for the
|
|
158
|
+
* same reason `layersFor` delegates rather than re-walking.
|
|
159
|
+
*/
|
|
160
|
+
export function missionResolutionFor(
|
|
161
|
+
key: string,
|
|
162
|
+
merged: unknown,
|
|
163
|
+
): AutoRunResolution | null {
|
|
164
|
+
if (key !== 'quality.local_auto_run') return null;
|
|
165
|
+
const configured = getSettingsLeaf(merged, 'quality.local_auto_run');
|
|
166
|
+
const missionValue = getSettingsLeaf(merged, 'quality.local_auto_run_in_mission');
|
|
167
|
+
return resolveLocalAutoRun({
|
|
168
|
+
configured: configured === true,
|
|
169
|
+
missionValue: typeof missionValue === 'boolean' ? missionValue : undefined,
|
|
170
|
+
inMission: true,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
145
174
|
/**
|
|
146
175
|
* `true` when the key is present in the user-global file but is NOT whitelisted,
|
|
147
176
|
* i.e. the loader read it and threw it away.
|
|
@@ -206,6 +235,7 @@ export function runSettingsGet(opts: SettingsGetOptions): SettingsGetResult {
|
|
|
206
235
|
const fallback = templateDefault(opts.packageRoot, opts.key);
|
|
207
236
|
const carveOut = carveOutFor(opts.key);
|
|
208
237
|
const drop = userGlobalDrop(opts.key);
|
|
238
|
+
const mission = missionResolutionFor(opts.key, merged);
|
|
209
239
|
|
|
210
240
|
if (opts.json) {
|
|
211
241
|
out.push(
|
|
@@ -223,6 +253,9 @@ export function runSettingsGet(opts: SettingsGetOptions): SettingsGetResult {
|
|
|
223
253
|
? { resolves_to: carveOut.absentResolvesTo, reader: carveOut.reader }
|
|
224
254
|
: null,
|
|
225
255
|
user_global_dropped: drop.dropped ? drop.file : null,
|
|
256
|
+
in_mission: mission === null
|
|
257
|
+
? null
|
|
258
|
+
: { value: mission.value, decided_by: mission.origin },
|
|
226
259
|
},
|
|
227
260
|
null,
|
|
228
261
|
2,
|
|
@@ -263,6 +296,17 @@ export function runSettingsGet(opts: SettingsGetOptions): SettingsGetResult {
|
|
|
263
296
|
);
|
|
264
297
|
}
|
|
265
298
|
|
|
299
|
+
if (mission !== null) {
|
|
300
|
+
out.push(
|
|
301
|
+
'',
|
|
302
|
+
`ℹ️ Inside a mission this key resolves to: ${JSON.stringify(mission.value)}`,
|
|
303
|
+
` Decided by: ${mission.origin === 'mission' ? 'quality.local_auto_run_in_mission' : 'the settings layers above'}`,
|
|
304
|
+
' A mission is an autonomous roadmap run with a claimed contract. The',
|
|
305
|
+
' resolution is a runtime condition, never a layer, so no file above',
|
|
306
|
+
' carries the mission value — see src/shared/missionExecution.ts.',
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
266
310
|
if (drop.dropped) {
|
|
267
311
|
out.push(
|
|
268
312
|
'',
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `execution` block of `agent-config doctor --json`.
|
|
3
|
+
*
|
|
4
|
+
* `road-to-adversarial-verification-and-long-runs` Phase 0.3. Its own module
|
|
5
|
+
* rather than a function inside `cmd_doctor.ts` for the reason
|
|
6
|
+
* `_lib/continuation_ladder.ts` states for itself: `check_source_size_budget` is
|
|
7
|
+
* a shrink-only ratchet and `cmd_doctor.ts` sits far past the 1,500-line cap, so
|
|
8
|
+
* every line added there is an added violation. A phase pays for its additions by
|
|
9
|
+
* putting new code in a file under the cap rather than by raising a baseline.
|
|
10
|
+
*
|
|
11
|
+
* `owner_owned_check` appears here as the ladder's last RUNG rather than as a
|
|
12
|
+
* separate field, because it is not a separate switch: it is the rung that
|
|
13
|
+
* decides whether the residue is owner-owned at all, and lifting it out would
|
|
14
|
+
* suggest it can be toggled independently of the order it sits in.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
executionPostureFromOverrides,
|
|
19
|
+
resolveExecutionPosture,
|
|
20
|
+
} from '../../shared/missionExecution.js';
|
|
21
|
+
import {
|
|
22
|
+
forgeProtectionRows,
|
|
23
|
+
protectionActions,
|
|
24
|
+
type ForgeReading,
|
|
25
|
+
} from '../_lib/forge_protection.js';
|
|
26
|
+
|
|
27
|
+
type Dict = Record<string, unknown>;
|
|
28
|
+
|
|
29
|
+
/** A settings-override stream, in the shape `iter_setting_overrides` yields. */
|
|
30
|
+
export type OverrideStream = Iterable<readonly [string, unknown, string]>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build the payload block from a settings-override stream.
|
|
34
|
+
*
|
|
35
|
+
* A stream that throws yields the defaults rather than propagating — a
|
|
36
|
+
* diagnostic that cannot report because its own input is malformed is the one
|
|
37
|
+
* shape `doctor` must never take.
|
|
38
|
+
*/
|
|
39
|
+
export function executionJson(overrides: () => OverrideStream): Dict {
|
|
40
|
+
let posture;
|
|
41
|
+
try {
|
|
42
|
+
posture = executionPostureFromOverrides(overrides());
|
|
43
|
+
} catch {
|
|
44
|
+
posture = resolveExecutionPosture({});
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
fix_loop_max: posture.fix_loop_max,
|
|
48
|
+
escalation: [...posture.escalation],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The `forge_protection` block of `doctor --json` — Phase 3.2.
|
|
54
|
+
*
|
|
55
|
+
* **`doctor` does NOT reach the network on its own.** A diagnostic that makes a
|
|
56
|
+
* `gh` call on every invocation is one nobody runs offline, and the sibling
|
|
57
|
+
* anchor gate already declined that cost for the same reason. So the reading is
|
|
58
|
+
* INJECTED: a caller that has queried the forge passes it, and a caller that has
|
|
59
|
+
* not passes the all-null reading, which produces five `unread` rows that still
|
|
60
|
+
* name the call each value would come from.
|
|
61
|
+
*
|
|
62
|
+
* That is the honest shape for *read, never guessed*: the block is always
|
|
63
|
+
* present, a value appears only when a named call produced it, and `unread` is a
|
|
64
|
+
* third state rather than a quiet `false`. A row that was never looked at and a
|
|
65
|
+
* row that was looked at and failed are different repairs.
|
|
66
|
+
*
|
|
67
|
+
* `actions` carries the human ACTION lines — the step's own words are that a
|
|
68
|
+
* missing row is a blocker entry and NOT a halt, so this reports and returns.
|
|
69
|
+
*/
|
|
70
|
+
export function forgeProtectionJson(reading: ForgeReading): Dict {
|
|
71
|
+
const rows = forgeProtectionRows(reading);
|
|
72
|
+
return {
|
|
73
|
+
rows: rows.map((r) => ({
|
|
74
|
+
id: r.id,
|
|
75
|
+
state: r.state,
|
|
76
|
+
source: r.source,
|
|
77
|
+
detail: r.detail,
|
|
78
|
+
})),
|
|
79
|
+
actions: protectionActions(rows),
|
|
80
|
+
read_from_forge: rows.some((r) => r.state !== 'unread'),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The all-null reading: nothing was queried. */
|
|
85
|
+
export const UNREAD_FORGE: ForgeReading = {
|
|
86
|
+
rulesets: null,
|
|
87
|
+
defaultBranch: null,
|
|
88
|
+
allowAutoMerge: null,
|
|
89
|
+
deployRestricted: null,
|
|
90
|
+
};
|