@event4u/agent-config 15.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 +301 -347
- 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 +77 -29
- 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 +97 -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/rules/user-interaction.md +31 -30
- 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/error-handling-patterns/SKILL.md +9 -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-NUHLI2RX.js +80 -0
- package/dist/cli-delegate/{chunk-HZ52BRGG.js → chunk-P6ZHP5W2.js} +15 -90
- package/dist/cli-delegate/{chunk-TYSY33MD.js → chunk-QJWAUMJW.js} +12 -2
- 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-2KKXJW25.js → chunk-S3VFXMTM.js} +1 -1
- 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 +918 -325
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +5 -3
- 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 +6 -6
- 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 +3 -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 +4 -4
- 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 +14 -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 +87 -40
- 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 +2601 -1073
- 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 +2008 -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 +1612 -667
- 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_lock.js +1 -1
- 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/io/yamlIO.js +50 -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 +28 -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/host-injection-effect.json +1 -1
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/preamble-payload-budget.json +12 -7
- package/src/config/preamble-payload-exceptions.json +28 -0
- 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/_dispatch.bash +3 -1
- 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/base_ref_payload.ts +315 -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/dropped_decision.ts +142 -0
- package/src/scripts/_lib/forge_protection.ts +267 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/installed_lock.ts +1 -1
- 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/measured_payload_ceiling.ts +424 -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/payload_catalogue_completeness.ts +211 -0
- package/src/scripts/_lib/release_findings_ingest.ts +324 -0
- 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/release_tag_dispatch.ts +63 -0
- package/src/scripts/_lib/review_baseline.ts +254 -0
- package/src/scripts/_lib/review_skipped_record.ts +61 -1
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/standing_bound_ratchet.ts +216 -103
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/turn_end_refusals.ts +51 -4
- 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_beta_review_markers.ts +35 -4
- package/src/scripts/check_enforcement_matrix.ts +624 -0
- package/src/scripts/check_finding_dispositions.ts +167 -16
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +954 -0
- package/src/scripts/check_held_object_arrivals.ts +381 -0
- package/src/scripts/check_preamble_payload_budget.ts +474 -124
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +567 -0
- package/src/scripts/check_release_published.ts +1 -1
- package/src/scripts/check_reply_consistency.ts +86 -17
- 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 +127 -12
- package/src/scripts/hooks/block_kernel_rule_writes.ts +12 -2
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/design_pass_hook.ts +128 -3
- package/src/scripts/hooks/end_review_nudge_hook.ts +101 -21
- 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/review_baseline_hook.ts +130 -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/turn_end_gate_hook.ts +232 -14
- package/src/scripts/hooks_doctor.ts +89 -1
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +107 -207
- package/src/scripts/interruption_report.ts +8 -2
- package/src/scripts/janitor.ts +15 -1
- 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/measure_turn_end_gate.ts +302 -5
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/release.ts +66 -162
- package/src/scripts/release_drill.ts +460 -15
- package/src/scripts/release_env.ts +1 -1
- package/src/scripts/release_publication.ts +52 -1
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_held_object_arrivals.ts +456 -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/sync_agent_settings.ts +140 -18
- package/src/scripts/trigger_eval_grandfather.json +0 -1
- package/src/scripts/ui_conformance_probe.ts +622 -0
- package/src/server/io/yamlIO.ts +48 -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,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cascade base — `road-to-adversarial-verification-and-long-runs` 5.1.
|
|
3
|
+
*
|
|
4
|
+
* `check_branch_freshness` asks whether a branch is behind THE BASE ITS PR
|
|
5
|
+
* TARGETS. That is one hop, and it is the right question for the common case
|
|
6
|
+
* where the base is the trunk. On a stacked PR it is only half of it: a branch
|
|
7
|
+
* targeting `feat/parent` can be perfectly current with `feat/parent` while
|
|
8
|
+
* `feat/parent` itself is fifty commits behind `main`, so the branch is stale
|
|
9
|
+
* against the tree it will eventually land in and every gate reports green.
|
|
10
|
+
*
|
|
11
|
+
* The cascade is the ordered list of refs a branch must be current with, nearest
|
|
12
|
+
* first. Merging in that order is not arbitrary: taking the trunk first would
|
|
13
|
+
* pull trunk commits past the parent and make the stack's own diff unreadable,
|
|
14
|
+
* which is the thing a stack exists to avoid.
|
|
15
|
+
*
|
|
16
|
+
* **Depth is bounded and the bound is deliberate.** A cascade is a chain of
|
|
17
|
+
* bases, and a chain can cycle — a branch whose PR targets a branch that targets
|
|
18
|
+
* it. `MAX_HOPS` stops the walk and the result says it was truncated, because
|
|
19
|
+
* a hop list silently cut short is indistinguishable from a short one.
|
|
20
|
+
*
|
|
21
|
+
* Pure: the caller supplies the base-of lookup, so the whole thing is testable
|
|
22
|
+
* without a forge.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** How many bases a cascade may chain through before the walk stops. */
|
|
26
|
+
export const MAX_HOPS = 8;
|
|
27
|
+
|
|
28
|
+
export interface Cascade {
|
|
29
|
+
/** Refs to merge in, nearest base first, trunk last. Never includes `branch`. */
|
|
30
|
+
readonly hops: readonly string[];
|
|
31
|
+
/** True when the walk stopped on a cycle rather than on the trunk. */
|
|
32
|
+
readonly cycle: boolean;
|
|
33
|
+
/** True when the walk stopped on {@link MAX_HOPS}. */
|
|
34
|
+
readonly truncated: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** `null` when the ref has no PR base — i.e. it is a trunk or is unknown. */
|
|
38
|
+
export type BaseOf = (ref: string) => string | null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Walk from `branch` to `trunk`, collecting every base on the way.
|
|
42
|
+
*
|
|
43
|
+
* A branch whose base IS the trunk yields one hop — identical to the single-hop
|
|
44
|
+
* behaviour that existed before, which is the compatibility property that
|
|
45
|
+
* matters: this widens the check, it does not change the common case.
|
|
46
|
+
*/
|
|
47
|
+
export function cascadeBase(branch: string, trunk: string, baseOf: BaseOf): Cascade {
|
|
48
|
+
const hops: string[] = [];
|
|
49
|
+
const seen = new Set<string>([branch]);
|
|
50
|
+
let current = branch;
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < MAX_HOPS; i += 1) {
|
|
53
|
+
const base = baseOf(current);
|
|
54
|
+
if (base === null || base === '') {
|
|
55
|
+
// No PR base. The trunk is still owed unless we are already on it.
|
|
56
|
+
if (current !== trunk && !hops.includes(trunk)) hops.push(trunk);
|
|
57
|
+
return { hops, cycle: false, truncated: false };
|
|
58
|
+
}
|
|
59
|
+
if (seen.has(base)) {
|
|
60
|
+
return { hops, cycle: true, truncated: false };
|
|
61
|
+
}
|
|
62
|
+
seen.add(base);
|
|
63
|
+
hops.push(base);
|
|
64
|
+
if (base === trunk) {
|
|
65
|
+
return { hops, cycle: false, truncated: false };
|
|
66
|
+
}
|
|
67
|
+
current = base;
|
|
68
|
+
}
|
|
69
|
+
return { hops, cycle: false, truncated: true };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One ref's freshness, as the caller measured it. */
|
|
73
|
+
export interface HopReading {
|
|
74
|
+
readonly ref: string;
|
|
75
|
+
/** `true` behind · `false` current · `null` not measured. */
|
|
76
|
+
readonly behind: boolean | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Which hops still need merging, in order.
|
|
81
|
+
*
|
|
82
|
+
* An UNMEASURED hop is returned alongside the behind ones rather than skipped:
|
|
83
|
+
* "we could not tell" and "it is current" are different states, and treating the
|
|
84
|
+
* first as the second is how a single-hop check reported green on a stale stack
|
|
85
|
+
* in the first place.
|
|
86
|
+
*/
|
|
87
|
+
export function hopsNeedingMerge(readings: readonly HopReading[]): HopReading[] {
|
|
88
|
+
return readings.filter((r) => r.behind !== false);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** True only when every hop was measured and every hop is current. */
|
|
92
|
+
export function cascadeCurrent(readings: readonly HopReading[]): boolean {
|
|
93
|
+
return readings.length > 0 && readings.every((r) => r.behind === false);
|
|
94
|
+
}
|
|
@@ -231,8 +231,13 @@ export function archive_file_header(boundary: string): string {
|
|
|
231
231
|
'> `tests/test_changelog_eras.py`.\n' +
|
|
232
232
|
'>\n' +
|
|
233
233
|
'> **Read-only.** New entries land in `CHANGELOG.md`. Entries\n' +
|
|
234
|
-
'> here are not
|
|
235
|
-
'> for what shipped
|
|
234
|
+
'> here are not rewritten — git tags remain the canonical source\n' +
|
|
235
|
+
'> for what shipped. The one amendment an archive permits is a\n' +
|
|
236
|
+
'> **correction that adds**: where a published entry says something\n' +
|
|
237
|
+
'> the shipped tree contradicts, the correction is appended beside\n' +
|
|
238
|
+
'> it and names the commit that decided the matter. Nothing is\n' +
|
|
239
|
+
'> deleted, so both the original claim and its correction stay\n' +
|
|
240
|
+
'> readable.\n' +
|
|
236
241
|
'>\n' +
|
|
237
242
|
'> Entry shape follows\n' +
|
|
238
243
|
'> [`../contracts/CHANGELOG-conventions.md`](../contracts/CHANGELOG-conventions.md).\n' +
|
|
@@ -60,6 +60,122 @@ export const HALT_ACTIONS: readonly LadderAction[] = [
|
|
|
60
60
|
'halt-premise-invalidated',
|
|
61
61
|
];
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* The delivery state machine — `road-to-adversarial-verification-and-long-runs`
|
|
65
|
+
* Phase 8.1, in the order a run walks it.
|
|
66
|
+
*
|
|
67
|
+
* A run's checkboxes and its DELIVERY are two different completions, and until
|
|
68
|
+
* now only the first one could end a run. That is the defect: a run whose
|
|
69
|
+
* roadmap reads zero-open while its PR sits on red CI, or on a target that moved
|
|
70
|
+
* underneath it, reported `complete` — a completion claim about work whose only
|
|
71
|
+
* evidence (the CI verdict on the final head) says otherwise.
|
|
72
|
+
*
|
|
73
|
+
* Two of these are ENDINGS and the rest are not:
|
|
74
|
+
* · `merged` — a grant existed and was spent.
|
|
75
|
+
* · `open-green` — no grant; the PR is open, its CI is green on the head CI
|
|
76
|
+
* actually observed, and the run says so. This is a success, not a
|
|
77
|
+
* shortfall: a run without a merge grant is not supposed to merge.
|
|
78
|
+
* Every earlier position means work remains, whatever the checkboxes read.
|
|
79
|
+
*/
|
|
80
|
+
export const DELIVERY_STATES = [
|
|
81
|
+
'working',
|
|
82
|
+
'local-green',
|
|
83
|
+
'pushed',
|
|
84
|
+
'pr-open',
|
|
85
|
+
'ci-pending',
|
|
86
|
+
'ci-red',
|
|
87
|
+
'target-sync-check',
|
|
88
|
+
'target-moved',
|
|
89
|
+
'delivery-ready',
|
|
90
|
+
'merged',
|
|
91
|
+
'open-green',
|
|
92
|
+
] as const;
|
|
93
|
+
export type DeliveryState = (typeof DELIVERY_STATES)[number];
|
|
94
|
+
|
|
95
|
+
/** The two positions at which a run has actually delivered. */
|
|
96
|
+
export const DELIVERY_ENDINGS: readonly DeliveryState[] = ['merged', 'open-green'];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* True when this delivery position means work remains.
|
|
100
|
+
*
|
|
101
|
+
* `null` — nothing recorded a position — is NOT incomplete. The ladder runs on
|
|
102
|
+
* the Stop path, where a `gh` probe is not affordable, so the position is READ
|
|
103
|
+
* from what the run wrote rather than measured here. An unrecorded position
|
|
104
|
+
* therefore means *this run does not report delivery*, and inventing
|
|
105
|
+
* incompleteness from that absence would hang every run that never adopted the
|
|
106
|
+
* field. Fail-open, in the one direction that cannot manufacture a stall.
|
|
107
|
+
*/
|
|
108
|
+
export function deliveryBlocksCompletion(state: DeliveryState | null | undefined): boolean {
|
|
109
|
+
if (state === null || state === undefined) return false;
|
|
110
|
+
return !DELIVERY_ENDINGS.includes(state);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The facts one terminal ledger line records about a run. */
|
|
114
|
+
export interface LedgerEventInput {
|
|
115
|
+
readonly action: LadderAction;
|
|
116
|
+
readonly runId: string;
|
|
117
|
+
readonly slug: string;
|
|
118
|
+
readonly turn: number;
|
|
119
|
+
readonly iterations: number;
|
|
120
|
+
readonly open: number;
|
|
121
|
+
readonly blocked: number;
|
|
122
|
+
readonly delivery: DeliveryState | null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build the terminal ledger line for a run.
|
|
127
|
+
*
|
|
128
|
+
* Extracted from `hooks/run_continuation_hook.ts` by
|
|
129
|
+
* `road-to-adversarial-verification-and-long-runs` 8.1, for the reason this
|
|
130
|
+
* module's own header gives: the hook sits past `check_source_size_budget`'s
|
|
131
|
+
* 1,500-line cap, where every added line is an added violation, so a phase pays
|
|
132
|
+
* for its additions by moving the code it is editing to a file under the cap.
|
|
133
|
+
* The `delivery` field is that addition.
|
|
134
|
+
*
|
|
135
|
+
* A ledger line is a persisted shape carrying a `RunTerminalState`, so it names
|
|
136
|
+
* the vocabulary version it was written against: a reader meeting an unknown
|
|
137
|
+
* value can then tell a newer writer from a corrupt row. `delivery` is OMITTED
|
|
138
|
+
* when the run reported no position rather than written as a null — `complete`
|
|
139
|
+
* alone cannot tell a merged run from an open-green one, and a null would read
|
|
140
|
+
* as "neither" rather than "unreported".
|
|
141
|
+
*/
|
|
142
|
+
export function buildLedgerEvent(
|
|
143
|
+
input: LedgerEventInput,
|
|
144
|
+
vocabularyVersion: number,
|
|
145
|
+
now: Date,
|
|
146
|
+
): Record<string, unknown> {
|
|
147
|
+
return {
|
|
148
|
+
event: input.action,
|
|
149
|
+
run_id: input.runId,
|
|
150
|
+
roadmap: input.slug,
|
|
151
|
+
turn: input.turn,
|
|
152
|
+
iterations: input.iterations,
|
|
153
|
+
open: input.open,
|
|
154
|
+
blocked: input.blocked,
|
|
155
|
+
terminal_state: terminalStateFor(input.action),
|
|
156
|
+
terminal_vocabulary_version: vocabularyVersion,
|
|
157
|
+
...(input.delivery !== null ? { delivery: input.delivery } : {}),
|
|
158
|
+
at: now.toISOString(),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Read a persisted delivery position tolerantly — `undefined` for anything this
|
|
164
|
+
* build does not recognise.
|
|
165
|
+
*
|
|
166
|
+
* Same forward-compatibility shape as {@link parseHaltStamp}, and the opposite
|
|
167
|
+
* fail direction on purpose: an unknown HALT is preserved, because dropping it
|
|
168
|
+
* re-engages a deliberately ended run; an unknown DELIVERY position is dropped,
|
|
169
|
+
* because preserving it would hold a run open on a state this build cannot
|
|
170
|
+
* evaluate. Both choose the branch that cannot manufacture a stall.
|
|
171
|
+
*/
|
|
172
|
+
export function parseDeliveryState(raw: unknown): DeliveryState | undefined {
|
|
173
|
+
if (typeof raw !== 'string') return undefined;
|
|
174
|
+
return (DELIVERY_STATES as readonly string[]).includes(raw)
|
|
175
|
+
? (raw as DeliveryState)
|
|
176
|
+
: undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
63
179
|
/**
|
|
64
180
|
* The subset of a run's state this decision reads. The hook's `RunState`
|
|
65
181
|
* satisfies it structurally; nothing here may widen beyond what the rungs use.
|
|
@@ -128,6 +244,10 @@ export function ladder(
|
|
|
128
244
|
// `_lib/context_observation.premiseMoved`. Optional and defaulted false, so
|
|
129
245
|
// a caller that cannot observe the world decides exactly as it did before.
|
|
130
246
|
premiseInvalidated: boolean = false,
|
|
247
|
+
// `road-to-adversarial-verification-and-long-runs` 8.1. The delivery position
|
|
248
|
+
// the run RECORDED, or `null` where it recorded none. Optional and defaulted,
|
|
249
|
+
// so a caller that does not track delivery decides exactly as it did before.
|
|
250
|
+
delivery: DeliveryState | null = null,
|
|
131
251
|
): LadderAction {
|
|
132
252
|
// A halt is terminal for this run id. Checked BEFORE `complete` so a
|
|
133
253
|
// halted run whose roadmap later reads zero-open does not report a
|
|
@@ -136,7 +256,26 @@ export function ladder(
|
|
|
136
256
|
// `scanOpenSteps` EXCLUDES `blocked-by:` steps from `openCount`, so zero-open
|
|
137
257
|
// with blocked steps left is exhaustion of runnable work, not completion —
|
|
138
258
|
// ADR-235's own terminal outcome, and never a sixth halt.
|
|
139
|
-
|
|
259
|
+
//
|
|
260
|
+
// 8.1 — delivery is checked INSIDE the zero-open branch and only against
|
|
261
|
+
// `complete`. Two boundaries, both deliberate. It does not touch `blocked`:
|
|
262
|
+
// a run whose remaining work is blocked has not failed to deliver, it has
|
|
263
|
+
// run out of deliverable work, and holding it open on a PR it cannot advance
|
|
264
|
+
// would convert a nameable blocker into a stall. And it is not checked while
|
|
265
|
+
// steps are still open, because there the run continues anyway — adding a
|
|
266
|
+
// second reason to continue would only make the ledger's `open` count lie
|
|
267
|
+
// about why.
|
|
268
|
+
const deliveringOnly =
|
|
269
|
+
openCount === 0 && blockedCount === 0 && deliveryBlocksCompletion(delivery);
|
|
270
|
+
if (openCount === 0) {
|
|
271
|
+
if (blockedCount > 0) return 'blocked';
|
|
272
|
+
if (!deliveringOnly) return 'complete';
|
|
273
|
+
// Fall THROUGH to the budget rungs rather than returning `engage` here.
|
|
274
|
+
// Returning early would put the run outside every bound in this function
|
|
275
|
+
// — an unbounded loop, which is the failure the ladder exists against —
|
|
276
|
+
// so a run held open for delivery is still capped by iterations and the
|
|
277
|
+
// wall clock. Only the STALL rung is exempted, below.
|
|
278
|
+
}
|
|
140
279
|
// BEFORE the counter rungs, deliberately: a missing credential, an absent
|
|
141
280
|
// binary or an exhausted quota is not closed by iterating, so spending the
|
|
142
281
|
// budget on it converts a nameable blocker into an anonymous cap-out.
|
|
@@ -151,8 +290,16 @@ export function ladder(
|
|
|
151
290
|
if (Number.isFinite(started) && nowMs - started >= caps.wallClockMs) {
|
|
152
291
|
return 'halt-wall-clock';
|
|
153
292
|
}
|
|
293
|
+
// The stall rung reads OPEN-STEP progress, and during delivery open-step
|
|
294
|
+
// progress is definitionally zero: the checkboxes are all flipped and the
|
|
295
|
+
// remaining work is a CI red or a moved target. Applying it here would halt
|
|
296
|
+
// a healthy delivery loop on its third turn — a stall manufactured by the
|
|
297
|
+
// stall detector, which is exactly the failure `run_continuation_hook`'s own
|
|
298
|
+
// header warns about in the abstract and the one this file's mechanics call
|
|
299
|
+
// "a metric that stops moving because the MEASUREMENT broke". The iteration
|
|
300
|
+
// and wall-clock caps above still bound the loop; only this rung is skipped.
|
|
154
301
|
const tail = state.history.slice(-caps.stallWindow);
|
|
155
|
-
if (tail.length >= caps.stallWindow && tail.every((n) => n === openCount)) {
|
|
302
|
+
if (!deliveringOnly && tail.length >= caps.stallWindow && tail.every((n) => n === openCount)) {
|
|
156
303
|
return 'halt-stall';
|
|
157
304
|
}
|
|
158
305
|
return 'engage';
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Council transport and cost — `road-to-adversarial-verification-and-long-runs`
|
|
3
|
+
* 10.1.
|
|
4
|
+
*
|
|
5
|
+
* The posture is CLI → CLI quota exhausted → API within the ceiling → API over
|
|
6
|
+
* the ceiling → **pause and report**. The last rung is the step's whole point:
|
|
7
|
+
* over the ceiling the run does not ask whether to buy more technical API usage.
|
|
8
|
+
* It reports.
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* AN OVER-CEILING COUNCIL REQUIREMENT PRODUCES A REPORT, NEVER A QUESTION.
|
|
12
|
+
* TECHNICAL API SPEND IS NOT A DECISION THE OWNER IS ASKED FOR MID-RUN.
|
|
13
|
+
* BUSINESS SPEND IS A TYPED OP AND A DIFFERENT CATEGORY ENTIRELY.
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* **Why a report rather than an ask, when both interrupt.** They do not
|
|
17
|
+
* interrupt the same way. An ask BLOCKS: the run stops until an answer arrives,
|
|
18
|
+
* and the thing being asked about — a few dollars of inference — is worth less
|
|
19
|
+
* than the run's remaining work. A report does not block: it says what needed the
|
|
20
|
+
* council, why the cheaper route was unavailable, what it would cost, and — the
|
|
21
|
+
* load-bearing field — **what can still proceed without it**. Most of a mission
|
|
22
|
+
* can.
|
|
23
|
+
*
|
|
24
|
+
* Pure.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** The routes, cheapest first. */
|
|
28
|
+
export type Route = 'cli' | 'api' | 'paused';
|
|
29
|
+
|
|
30
|
+
export interface TransportInput {
|
|
31
|
+
/** Is the CLI route configured and within quota? */
|
|
32
|
+
readonly cliAvailable: boolean;
|
|
33
|
+
/** Why the CLI is unavailable, when it is not. */
|
|
34
|
+
readonly cliUnavailableReason: string | null;
|
|
35
|
+
/** Estimated USD for the API route. */
|
|
36
|
+
readonly estimateUsd: number;
|
|
37
|
+
/** The run's remaining technical-spend ceiling in USD. */
|
|
38
|
+
readonly ceilingUsd: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PauseReport {
|
|
42
|
+
readonly needed: string;
|
|
43
|
+
readonly cliUnavailableBecause: string;
|
|
44
|
+
readonly estimateUsd: number;
|
|
45
|
+
readonly ceilingUsd: number;
|
|
46
|
+
readonly missionState: string;
|
|
47
|
+
/** What the run continues with meanwhile. Empty is a real, reportable answer. */
|
|
48
|
+
readonly canStillProceed: readonly string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TransportDecision {
|
|
52
|
+
readonly route: Route;
|
|
53
|
+
readonly reason: string;
|
|
54
|
+
/** Present only on `paused`. */
|
|
55
|
+
readonly report: PauseReport | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PauseContext {
|
|
59
|
+
readonly needed: string;
|
|
60
|
+
readonly missionState: string;
|
|
61
|
+
readonly canStillProceed: readonly string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Route one council requirement.
|
|
66
|
+
*
|
|
67
|
+
* A ceiling of zero pauses rather than dividing by it, and an estimate exactly
|
|
68
|
+
* AT the ceiling is within it — the ceiling is a limit, not an exclusive bound,
|
|
69
|
+
* and reading it the other way would pause a run that had budgeted exactly.
|
|
70
|
+
*/
|
|
71
|
+
export function routeCouncil(input: TransportInput, ctx: PauseContext): TransportDecision {
|
|
72
|
+
if (input.cliAvailable) {
|
|
73
|
+
return { route: 'cli', reason: 'the CLI route is available and within quota', report: null };
|
|
74
|
+
}
|
|
75
|
+
const why = input.cliUnavailableReason ?? 'unstated';
|
|
76
|
+
if (input.estimateUsd <= input.ceilingUsd) {
|
|
77
|
+
return {
|
|
78
|
+
route: 'api',
|
|
79
|
+
reason: `CLI unavailable (${why}); the API estimate $${input.estimateUsd.toFixed(2)} is within the $${input.ceilingUsd.toFixed(2)} ceiling`,
|
|
80
|
+
report: null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
route: 'paused',
|
|
85
|
+
reason: `the API estimate $${input.estimateUsd.toFixed(2)} exceeds the $${input.ceilingUsd.toFixed(2)} ceiling`,
|
|
86
|
+
report: {
|
|
87
|
+
needed: ctx.needed,
|
|
88
|
+
cliUnavailableBecause: why,
|
|
89
|
+
estimateUsd: input.estimateUsd,
|
|
90
|
+
ceilingUsd: input.ceilingUsd,
|
|
91
|
+
missionState: ctx.missionState,
|
|
92
|
+
canStillProceed: ctx.canStillProceed,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The six fields a pause report must carry. */
|
|
98
|
+
export const REPORT_FIELDS: readonly (keyof PauseReport)[] = [
|
|
99
|
+
'needed',
|
|
100
|
+
'cliUnavailableBecause',
|
|
101
|
+
'estimateUsd',
|
|
102
|
+
'ceilingUsd',
|
|
103
|
+
'missionState',
|
|
104
|
+
'canStillProceed',
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Render the report.
|
|
109
|
+
*
|
|
110
|
+
* It ends on what can still proceed, and that placement is deliberate: a reader
|
|
111
|
+
* who stops after the cost has read a complaint, and a reader who reaches the
|
|
112
|
+
* last line has read a status. Nothing here is phrased as a question, and the
|
|
113
|
+
* renderer emits no `?` — asserted by a test, because the failure mode is a
|
|
114
|
+
* report that drifts into an ask one helpful sentence at a time.
|
|
115
|
+
*/
|
|
116
|
+
export function renderReport(r: PauseReport): string {
|
|
117
|
+
const proceeding =
|
|
118
|
+
r.canStillProceed.length > 0
|
|
119
|
+
? r.canStillProceed.map((x) => ` - ${x}`).join('\n')
|
|
120
|
+
: ' - nothing; the mission is blocked on this council pass alone';
|
|
121
|
+
return [
|
|
122
|
+
`Council paused: ${r.needed}`,
|
|
123
|
+
`CLI route unavailable: ${r.cliUnavailableBecause}`,
|
|
124
|
+
`API estimate $${r.estimateUsd.toFixed(2)} over the $${r.ceilingUsd.toFixed(2)} technical ceiling`,
|
|
125
|
+
`Mission state: ${r.missionState}`,
|
|
126
|
+
'Proceeding meanwhile with:',
|
|
127
|
+
proceeding,
|
|
128
|
+
].join('\n');
|
|
129
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is a PR delivery-ready? — `road-to-adversarial-verification-and-long-runs` 3.1.
|
|
3
|
+
*
|
|
4
|
+
* The step's acceptance clause is that *a fixture PR with a disabled required
|
|
5
|
+
* check cannot reach delivery-ready*, and that is the one case a naive reader
|
|
6
|
+
* gets wrong: a PR whose required context was SKIPPED has a green rollup. GitHub
|
|
7
|
+
* reports `SKIPPED` as a non-failure, so a predicate that asks "did anything
|
|
8
|
+
* fail" answers no, and the gate the context was supposed to enforce did not run.
|
|
9
|
+
*
|
|
10
|
+
* So this asks two questions rather than one: did every required context report,
|
|
11
|
+
* and did each of those reports actually pass. A context that is missing from
|
|
12
|
+
* the rollup entirely fails the first; a context present but skipped, cancelled
|
|
13
|
+
* or neutral fails the second.
|
|
14
|
+
*
|
|
15
|
+
* Pure — the caller supplies the rollup and the required set, so both polarities
|
|
16
|
+
* are testable without a forge.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** One check as a forge rollup reports it. */
|
|
20
|
+
export interface CheckRun {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
/** `SUCCESS` · `FAILURE` · `SKIPPED` · `CANCELLED` · `NEUTRAL` · `null` while running. */
|
|
23
|
+
readonly conclusion: string | null;
|
|
24
|
+
/** `COMPLETED` · `IN_PROGRESS` · `QUEUED`. */
|
|
25
|
+
readonly status: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Why a PR is not delivery-ready. Empty means it is. */
|
|
29
|
+
export type DeliveryBlock =
|
|
30
|
+
| { readonly kind: 'required-context-absent'; readonly context: string }
|
|
31
|
+
| { readonly kind: 'required-context-not-passing'; readonly context: string; readonly conclusion: string }
|
|
32
|
+
| { readonly kind: 'still-running'; readonly context: string }
|
|
33
|
+
| { readonly kind: 'stale-head'; readonly observed: string; readonly head: string };
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The only conclusion that counts as a required check having done its job.
|
|
37
|
+
*
|
|
38
|
+
* `SKIPPED` is deliberately NOT here, and it is the reason this module exists:
|
|
39
|
+
* a skipped required check is an unenforced gate wearing a green rollup.
|
|
40
|
+
* `NEUTRAL` and `CANCELLED` are excluded on the same ground — a check that
|
|
41
|
+
* reached no verdict enforced nothing, whatever colour it rendered.
|
|
42
|
+
*/
|
|
43
|
+
const PASSING = 'SUCCESS';
|
|
44
|
+
|
|
45
|
+
export interface DeliveryInput {
|
|
46
|
+
/** Contexts the forge requires, from `forge_protection`'s ruleset read. */
|
|
47
|
+
readonly requiredContexts: readonly string[];
|
|
48
|
+
/** The rollup on the head under test. */
|
|
49
|
+
readonly checks: readonly CheckRun[];
|
|
50
|
+
/** The head CI actually observed. */
|
|
51
|
+
readonly observedHead: string;
|
|
52
|
+
/** The branch head right now. */
|
|
53
|
+
readonly head: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Every reason this PR is not delivery-ready, in report order.
|
|
58
|
+
*
|
|
59
|
+
* Returns ALL blocks rather than the first: a run that fixes one and re-pushes
|
|
60
|
+
* to discover the next has spent a CI cycle per block, which is the cost layer 1
|
|
61
|
+
* of the delivery contract exists to avoid paying at layer 4.
|
|
62
|
+
*/
|
|
63
|
+
export function deliveryBlocks(input: DeliveryInput): DeliveryBlock[] {
|
|
64
|
+
const out: DeliveryBlock[] = [];
|
|
65
|
+
const byName = new Map(input.checks.map((c) => [c.name, c]));
|
|
66
|
+
|
|
67
|
+
for (const context of input.requiredContexts) {
|
|
68
|
+
const run = byName.get(context);
|
|
69
|
+
if (run === undefined) {
|
|
70
|
+
out.push({ kind: 'required-context-absent', context });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (run.status !== 'COMPLETED') {
|
|
74
|
+
out.push({ kind: 'still-running', context });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (run.conclusion !== PASSING) {
|
|
78
|
+
out.push({
|
|
79
|
+
kind: 'required-context-not-passing',
|
|
80
|
+
context,
|
|
81
|
+
conclusion: run.conclusion ?? 'null',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Checked LAST and always: a green rollup on a head that is no longer the
|
|
87
|
+
// branch head is a verdict about a different tree. `ci_settle` names the head
|
|
88
|
+
// its verdict is about precisely so this comparison is possible.
|
|
89
|
+
if (input.observedHead !== input.head) {
|
|
90
|
+
out.push({ kind: 'stale-head', observed: input.observedHead, head: input.head });
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** True only when nothing blocks. */
|
|
96
|
+
export function isDeliveryReady(input: DeliveryInput): boolean {
|
|
97
|
+
return deliveryBlocks(input).length === 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** One human-readable line per block. */
|
|
101
|
+
export function renderBlock(b: DeliveryBlock): string {
|
|
102
|
+
switch (b.kind) {
|
|
103
|
+
case 'required-context-absent':
|
|
104
|
+
return `required check "${b.context}" did not report at all`;
|
|
105
|
+
case 'required-context-not-passing':
|
|
106
|
+
return `required check "${b.context}" concluded ${b.conclusion}, not SUCCESS — a skipped or cancelled required check is an unenforced gate, not a pass`;
|
|
107
|
+
case 'still-running':
|
|
108
|
+
return `required check "${b.context}" has not completed`;
|
|
109
|
+
case 'stale-head':
|
|
110
|
+
return `CI was observed on ${b.observed} but the branch head is ${b.head} — the verdict describes a different tree`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detector E of `turn-end-gate`, extracted so its prose costs nothing.
|
|
3
|
+
*
|
|
4
|
+
* It lives here rather than in the hook for one mechanical reason and one real
|
|
5
|
+
* one. The mechanical one: `turn_end_gate_hook.ts` sits past the 1500-line
|
|
6
|
+
* source ceiling, where every line is one unit of ratchet debt, and this module
|
|
7
|
+
* is under it. The real one: E is the only detector that reads a LIST of the
|
|
8
|
+
* turn's assistant texts rather than the single closing one, so it shares no
|
|
9
|
+
* input with A-D and F, and its dependencies are the reply-consistency parser
|
|
10
|
+
* rather than anything the hook owns.
|
|
11
|
+
*/
|
|
12
|
+
import {
|
|
13
|
+
find_option_blocks,
|
|
14
|
+
recommendationsUnder,
|
|
15
|
+
splitAndMask,
|
|
16
|
+
} from '../check_reply_consistency.js';
|
|
17
|
+
import type { RefusalDetectorId } from './turn_end_refusals.js';
|
|
18
|
+
|
|
19
|
+
/** The shape the gate collects. Kept structural so this module imports no hook. */
|
|
20
|
+
export interface DroppedDecisionFinding {
|
|
21
|
+
detector: RefusalDetectorId;
|
|
22
|
+
evidence: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Detector E — a decision put to the user in this turn, dropped before the turn
|
|
28
|
+
* ended.
|
|
29
|
+
*
|
|
30
|
+
* THE MEASURED FAILURE. An assistant turn put three numbered options and a
|
|
31
|
+
* recommendation line to the user. The `end-review-nudge` stop concern then
|
|
32
|
+
* produced a SECOND assistant execution with no intervening user message, and
|
|
33
|
+
* that second reply handled the nudge while demoting the open question to a
|
|
34
|
+
* subordinate clause — "bleibt davon unberührt und liegt bei Dir". From the
|
|
35
|
+
* user's side the conversation ended without a question. No rule stated this
|
|
36
|
+
* when the detector was written: `user-interaction` Iron Law 1 inspects a reply
|
|
37
|
+
* that HAS an options block, `check_reply_consistency` receives a draft and not
|
|
38
|
+
* a transcript, `active-remediation` governs findings rather than issued
|
|
39
|
+
* decisions, and `no-cheap-questions` guards the opposite direction. That gap
|
|
40
|
+
* is now closed from the other side — `user-interaction` Iron Law 3 states the
|
|
41
|
+
* continuity obligation, and the refusal below cites it — so this paragraph
|
|
42
|
+
* records why the detector exists, not a gap that is still open.
|
|
43
|
+
*
|
|
44
|
+
* WHAT IT READS. `assistantTurnTexts` — the assistant texts since the last
|
|
45
|
+
* GENUINE user prompt, with synthetic prompts and sidechain entries already
|
|
46
|
+
* filtered by `readTranscriptTail`. So a task notification, a system reminder or
|
|
47
|
+
* a subagent's own prompt does not read as an answer, which is the whole point:
|
|
48
|
+
* the failure is defined by the absence of a real user turn.
|
|
49
|
+
*
|
|
50
|
+
* WHAT IT DELIBERATELY DOES NOT DO. It does not model the decision — no id, no
|
|
51
|
+
* `pending → answered` lifecycle, no semantic matching of a free-text reply
|
|
52
|
+
* against an option. That object is grant-shaped and
|
|
53
|
+
* `road-to-decision-closure` owns its ownership routing; a second obligation
|
|
54
|
+
* ledger here would be the parallel taxonomy that roadmap exists to prevent.
|
|
55
|
+
* This is transcript adjacency and nothing above it.
|
|
56
|
+
*
|
|
57
|
+
* AN ASK IS A BLOCK **PLUS** ITS RECOMMENDATION LINE, and that qualifier is
|
|
58
|
+
* load-bearing rather than pedantic. `find_option_blocks` calls any run of two
|
|
59
|
+
* or more consecutive numbered lines a block — a plan, a findings list, an
|
|
60
|
+
* ordinary enumeration. Keyed on the block alone this detector fired on 29 of
|
|
61
|
+
* this repository's own 592 assistant turns, and 8 of those blocks carried no
|
|
62
|
+
* recommendation line anywhere near them: pure false positives on narrative
|
|
63
|
+
* lists, in a detector that can REFUSE a turn. `user-interaction` Iron Law 1
|
|
64
|
+
* already says the recommendation line is the ask, so requiring it removes that
|
|
65
|
+
* whole class by applying the rule this refusal cites rather than by bolting a
|
|
66
|
+
* heuristic beside it.
|
|
67
|
+
*
|
|
68
|
+
* The cost, stated rather than discovered: an options block whose recommendation
|
|
69
|
+
* line was omitted is invisible here. That reply is ALREADY a
|
|
70
|
+
* `check_reply_consistency` finding and a stated Iron Law 1 violation, so it is
|
|
71
|
+
* the malformed-ask problem rather than the dropped-ask problem — a different
|
|
72
|
+
* defect with a different owner.
|
|
73
|
+
*
|
|
74
|
+
* REUSE. `find_option_blocks` and `recommendationsUnder` are both the
|
|
75
|
+
* spec-backed readings behind `check_reply_consistency`, extracted rather than
|
|
76
|
+
* copied. `interruption_ledger_hook` carries its own `hasNumberedOptions` for
|
|
77
|
+
* the capture-only ledger; a third reading of the same rule would drift from
|
|
78
|
+
* both.
|
|
79
|
+
*
|
|
80
|
+
* TWO KNOWN FALSE-POSITIVE PATHS, named because a refusing detector owes its
|
|
81
|
+
* residual rather than only its fix:
|
|
82
|
+
*
|
|
83
|
+
* · A BLOCKQUOTED illustration of an ask — `> 1.` / `> 2.` plus a quoted
|
|
84
|
+
* recommendation line — reads as live. `OPTION_LINE_RE` accepts a leading
|
|
85
|
+
* `>` deliberately, because this suite's own documented ask shape is often
|
|
86
|
+
* blockquoted, so excluding it would miss real asks to avoid a quoted one.
|
|
87
|
+
* Fenced illustrations ARE excluded and tested; blockquoted ones are not,
|
|
88
|
+
* and narrowing the shared parser to fix that would change what
|
|
89
|
+
* `check_reply_consistency` enforces about Iron Law 1.
|
|
90
|
+
* · A genuine user turn carrying NO `type: 'text'` block — an image-only
|
|
91
|
+
* message — does not reset the turn, because `readTranscriptTail` skips an
|
|
92
|
+
* entry with no text before reaching the reset. Changing that would move
|
|
93
|
+
* `turnOrdinal` for all four other detectors on a shape none of them has
|
|
94
|
+
* been measured against.
|
|
95
|
+
*
|
|
96
|
+
* Both cost one extra line in a reply, once: the re-entrancy layers cap a turn
|
|
97
|
+
* at ONE refusal, so neither can wedge a session.
|
|
98
|
+
*/
|
|
99
|
+
export function detectDroppedDecision(
|
|
100
|
+
assistantTurnTexts: readonly string[],
|
|
101
|
+
closingFromPayload = false,
|
|
102
|
+
): DroppedDecisionFinding | null {
|
|
103
|
+
if (assistantTurnTexts.length < 2) return null;
|
|
104
|
+
// Masked once per text, not once per block: this runs on the stop path over
|
|
105
|
+
// every assistant text of the turn, and a reply with four numbered blocks
|
|
106
|
+
// would otherwise split and fence-mask the whole reply four times.
|
|
107
|
+
const asksIn = (text: string): ReturnType<typeof find_option_blocks> => {
|
|
108
|
+
const blocks = find_option_blocks(text);
|
|
109
|
+
if (blocks.length === 0) return blocks;
|
|
110
|
+
const masked = splitAndMask(text);
|
|
111
|
+
return blocks.filter((b) => recommendationsUnder(text, b, masked).length > 0);
|
|
112
|
+
};
|
|
113
|
+
const closing = assistantTurnTexts[assistantTurnTexts.length - 1]!;
|
|
114
|
+
if (asksIn(closing).length > 0) return null;
|
|
115
|
+
for (let i = assistantTurnTexts.length - 2; i >= 0; i -= 1) {
|
|
116
|
+
const earlier = assistantTurnTexts[i]!;
|
|
117
|
+
const blocks = asksIn(earlier);
|
|
118
|
+
if (blocks.length === 0) continue;
|
|
119
|
+
const block = blocks[blocks.length - 1]!;
|
|
120
|
+
return {
|
|
121
|
+
detector: 'pending-decision',
|
|
122
|
+
// The option NUMBERS and the line span, never the option text: the
|
|
123
|
+
// evidence is quoted into a refusal that reaches the transcript, and
|
|
124
|
+
// an options block can carry anything the reply carried.
|
|
125
|
+
evidence:
|
|
126
|
+
`options ${block.numbers.join('/')} at lines ${block.startLine}-${block.endLine} ` +
|
|
127
|
+
`of an earlier reply this turn (closing reply read from ` +
|
|
128
|
+
`${closingFromPayload ? 'the stop payload' : 'the transcript'})`,
|
|
129
|
+
reason:
|
|
130
|
+
'an earlier reply in THIS turn put numbered options to the user ' +
|
|
131
|
+
'and no user answer followed — this closing reply carries no ' +
|
|
132
|
+
'options block, so the decision disappeared without being ' +
|
|
133
|
+
'answered, cancelled or superseded. A hook nudge, a reviewer ' +
|
|
134
|
+
'result or a task notification may ADD to a turn; it may not ' +
|
|
135
|
+
'displace a question already asked (user-interaction Iron Law 3: ' +
|
|
136
|
+
'a decision handed to the user outlives the turn). ' +
|
|
137
|
+
'Re-present the block and its recommendation line at the end of ' +
|
|
138
|
+
'this reply',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|