@event4u/agent-config 16.1.0 → 16.2.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 +237 -248
- package/dist/agent-src/commands/roadmap/process-full.md +1 -1
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- package/dist/agent-src/skills/design-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +16 -1
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-7XGBX6ZH.js} +471 -387
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-FP36SWJD.js} +432 -144
- package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-IGD6ERX6.js} +31 -0
- package/dist/cli-delegate/{chunk-FXYUYU4N.js → chunk-JNMVLWLR.js} +42 -1
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- package/dist/cli-delegate/cmd_conformance.js +902 -782
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +10 -10
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +6666 -2581
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/install.mjs +848 -733
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2747 -1510
- package/dist/server/schemas/settings.js +23 -0
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +6 -4
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +29 -0
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +2 -2
- package/src/config/gate-coverage.yml +234 -3
- package/src/config/gate-violation-baselines.json +7 -3
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +2 -2
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/scripts/_cli/cmd_doctor.ts +5 -19
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +245 -3
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- package/src/scripts/compile_hook_manifest.ts +60 -16
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +42 -5
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +8 -5
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/install.ts +3 -4
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +55 -0
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +1 -1
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -2
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +31 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
|
@@ -8,6 +8,50 @@
|
|
|
8
8
|
"additionalProperties": false,
|
|
9
9
|
"required": ["name", "description", "disable-model-invocation"],
|
|
10
10
|
"properties": {
|
|
11
|
+
"inputs": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"minItems": 1,
|
|
14
|
+
"description": "Structured declaration of the parameters this artifact takes. OPTIONAL on every artifact: one with no block validates unchanged, so nothing existing breaks and no migration is owed. Where the block IS present it is the single authority — the argument-hint and the MCP `arguments` set are derived from it, and a hand-written hint on the same artifact is a conflict rather than a merge (road-to-an-invocation-contract-that-reaches-the-wire).",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"required": ["name", "type"],
|
|
19
|
+
"properties": {
|
|
20
|
+
"name": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"pattern": "^[a-z][a-z0-9_]*$",
|
|
23
|
+
"description": "Parameter name. Lowercase snake_case, so it is safe both as a wire key and as an in-body ${name} reference."
|
|
24
|
+
},
|
|
25
|
+
"type": {
|
|
26
|
+
"enum": ["string", "number", "boolean", "enum", "path"],
|
|
27
|
+
"description": "Closed set on purpose: an unknown type must FAIL validation rather than reach a host that cannot interpret it."
|
|
28
|
+
},
|
|
29
|
+
"required": {
|
|
30
|
+
"type": "boolean",
|
|
31
|
+
"description": "Absent means optional."
|
|
32
|
+
},
|
|
33
|
+
"default": {
|
|
34
|
+
"description": "Value used when the caller supplies none. Any JSON type — it is checked against `type` by the consuming generator, not here."
|
|
35
|
+
},
|
|
36
|
+
"enum": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"minItems": 1,
|
|
39
|
+
"items": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1,
|
|
42
|
+
"pattern": "^[^|\\]>]+$",
|
|
43
|
+
"description": "A value. `|`, `]` and `>` are excluded because they are the delimiters of the derived argument-hint — permitting them would make the hint ambiguous and break the round-trip the hint gate asserts."
|
|
44
|
+
},
|
|
45
|
+
"description": "Allowed values. Only meaningful when `type` is `enum`."
|
|
46
|
+
},
|
|
47
|
+
"description": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"minLength": 1,
|
|
50
|
+
"description": "One line, shown to a caller choosing a value."
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
11
55
|
"name": {
|
|
12
56
|
"type": "string",
|
|
13
57
|
"pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
|
|
@@ -127,6 +127,10 @@
|
|
|
127
127
|
"type": "string",
|
|
128
128
|
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
|
|
129
129
|
"description": "ISO calendar date of the disposition."
|
|
130
|
+
},
|
|
131
|
+
"contradicted": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"description": "The tree's mechanical disproof of the finding's premise, written by self_review_gate.annotateContradicted when a deletion claim names an artifact the range does not delete and the working tree still holds. A finding carrying this is NOT merge-blocking in either gate (self_review_gate.classifyBlocking and check_finding_dispositions.isBlocking both read it), and it is still reported so a human sees what was claimed and why it was set aside."
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
}
|
|
@@ -13,6 +13,50 @@
|
|
|
13
13
|
"domain"
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
|
+
"inputs": {
|
|
17
|
+
"type": "array",
|
|
18
|
+
"minItems": 1,
|
|
19
|
+
"description": "Structured declaration of the parameters this artifact takes. OPTIONAL on every artifact: one with no block validates unchanged, so nothing existing breaks and no migration is owed. Where the block IS present it is the single authority — the argument-hint and the MCP `arguments` set are derived from it, and a hand-written hint on the same artifact is a conflict rather than a merge (road-to-an-invocation-contract-that-reaches-the-wire).",
|
|
20
|
+
"items": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"required": ["name", "type"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"name": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"pattern": "^[a-z][a-z0-9_]*$",
|
|
28
|
+
"description": "Parameter name. Lowercase snake_case, so it is safe both as a wire key and as an in-body ${name} reference."
|
|
29
|
+
},
|
|
30
|
+
"type": {
|
|
31
|
+
"enum": ["string", "number", "boolean", "enum", "path"],
|
|
32
|
+
"description": "Closed set on purpose: an unknown type must FAIL validation rather than reach a host that cannot interpret it."
|
|
33
|
+
},
|
|
34
|
+
"required": {
|
|
35
|
+
"type": "boolean",
|
|
36
|
+
"description": "Absent means optional."
|
|
37
|
+
},
|
|
38
|
+
"default": {
|
|
39
|
+
"description": "Value used when the caller supplies none. Any JSON type — it is checked against `type` by the consuming generator, not here."
|
|
40
|
+
},
|
|
41
|
+
"enum": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"minItems": 1,
|
|
44
|
+
"items": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"minLength": 1,
|
|
47
|
+
"pattern": "^[^|\\]>]+$",
|
|
48
|
+
"description": "A value. `|`, `]` and `>` are excluded because they are the delimiters of the derived argument-hint — permitting them would make the hint ambiguous and break the round-trip the hint gate asserts."
|
|
49
|
+
},
|
|
50
|
+
"description": "Allowed values. Only meaningful when `type` is `enum`."
|
|
51
|
+
},
|
|
52
|
+
"description": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"minLength": 1,
|
|
55
|
+
"description": "One line, shown to a caller choosing a value."
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
16
60
|
"name": {
|
|
17
61
|
"type": "string",
|
|
18
62
|
"pattern": "^[a-z][a-z0-9-]*$",
|
|
@@ -50,6 +50,15 @@ export interface Finding {
|
|
|
50
50
|
title: string;
|
|
51
51
|
detail: string;
|
|
52
52
|
file?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Set when the tree mechanically DISPROVES the finding's premise — today
|
|
55
|
+
* only for the deletion class: the finding says an artifact was removed,
|
|
56
|
+
* the range deletes nothing by that name, and the artifact is still in the
|
|
57
|
+
* tree. Carries the disproof, so the finding stays readable rather than
|
|
58
|
+
* disappearing. A finding carrying this is not merge-blocking; see
|
|
59
|
+
* `classifyBlocking`.
|
|
60
|
+
*/
|
|
61
|
+
contradicted?: string;
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
/**
|
|
@@ -74,6 +83,13 @@ export function findingId(f: Pick<Finding, 'kind' | 'title' | 'file'>): string {
|
|
|
74
83
|
* block ONLY on the narrow security/claim × {critical,high} intersection.
|
|
75
84
|
*/
|
|
76
85
|
export function classifyBlocking(f: Finding): boolean {
|
|
86
|
+
// A premise the tree DISPROVES cannot block a merge. This is not leniency
|
|
87
|
+
// and not a heuristic: `contradicted` is set only where a deletion claim
|
|
88
|
+
// was checked against `git diff --name-status base...HEAD` AND against the
|
|
89
|
+
// working tree, and both said the named artifact is present and undeleted.
|
|
90
|
+
// See `contradictedByTree` for why this class needed a mechanical disproof
|
|
91
|
+
// rather than one more sentence in the prompt.
|
|
92
|
+
if ((f.contradicted ?? '').trim() !== '') return false;
|
|
77
93
|
return (f.kind === 'security' || f.kind === 'claim') && (f.severity === 'critical' || f.severity === 'high');
|
|
78
94
|
}
|
|
79
95
|
|
|
@@ -120,6 +136,271 @@ function changedFiles(baseRef: string, cwd: string = REPO_ROOT): string[] {
|
|
|
120
136
|
.filter(isReviewablePath);
|
|
121
137
|
}
|
|
122
138
|
|
|
139
|
+
/**
|
|
140
|
+
* The paths the range actually DELETES, from `git diff --name-status`.
|
|
141
|
+
*
|
|
142
|
+
* Deliberately NOT filtered by `isReviewablePath`: the question this answers is
|
|
143
|
+
* "did the range remove this artifact", and a removal under `dist/agent-src/`
|
|
144
|
+
* is a removal even though the projection is never sent to the reviewer. A
|
|
145
|
+
* filter here would turn a real deletion into a disproof of itself.
|
|
146
|
+
*
|
|
147
|
+
* Renames are reported by `--name-status` as `R<score>\told\tnew`, and the old
|
|
148
|
+
* path IS gone, so it is counted. A roadmap moved into `archive/` shows up as a
|
|
149
|
+
* genuine deletion of its old path, which is exactly the reading a finding
|
|
150
|
+
* about that path deserves.
|
|
151
|
+
*/
|
|
152
|
+
export function rangeDeletions(baseRef: string, cwd: string = REPO_ROOT): Set<string> {
|
|
153
|
+
return parseDeletions(nameStatusText(baseRef, cwd));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The paths the range MODIFIES, adds, or renames TO — everything it touched
|
|
158
|
+
* that still has a path afterwards.
|
|
159
|
+
*
|
|
160
|
+
* The complement of `rangeDeletions` over the same `--name-status` output, and
|
|
161
|
+
* the input `contradictedByTree` needs to tell "the range did not touch this"
|
|
162
|
+
* from "the range changed the inside of this". Unfiltered for the same reason
|
|
163
|
+
* `rangeDeletions` is: the question is what the RANGE did, not what the
|
|
164
|
+
* reviewer was sent.
|
|
165
|
+
*/
|
|
166
|
+
export function rangeModifications(baseRef: string, cwd: string = REPO_ROOT): Set<string> {
|
|
167
|
+
return parseModifications(nameStatusText(baseRef, cwd));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** One `--name-status` read, shared by both parses so the range is read once. */
|
|
171
|
+
function nameStatusText(baseRef: string, cwd: string = REPO_ROOT): string {
|
|
172
|
+
const r = spawnSync('git', ['-c', 'core.quotePath=false', 'diff', '--name-status', `${baseRef}...HEAD`], {
|
|
173
|
+
cwd,
|
|
174
|
+
encoding: 'utf8',
|
|
175
|
+
});
|
|
176
|
+
if (r.status !== 0) {
|
|
177
|
+
throw new Error(`git diff --name-status failed: ${(r.stderr ?? '').toString().slice(0, 300)}`);
|
|
178
|
+
}
|
|
179
|
+
return r.stdout ?? '';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Pure core of `rangeDeletions`, so the parse is testable without a repo. */
|
|
183
|
+
export function parseDeletions(nameStatus: string): Set<string> {
|
|
184
|
+
const out = new Set<string>();
|
|
185
|
+
for (const line of nameStatus.split('\n')) {
|
|
186
|
+
const cols = line.split('\t');
|
|
187
|
+
const status = (cols[0] ?? '').trim();
|
|
188
|
+
if (status === '') continue;
|
|
189
|
+
if (status === 'D' && cols[1]) out.add(cols[1].trim());
|
|
190
|
+
// A rename removes its OLD path; `R100\told\tnew` puts it in column 1.
|
|
191
|
+
if (status.startsWith('R') && cols[1]) out.add(cols[1].trim());
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Pure core of `rangeModifications` — every path the range left standing.
|
|
198
|
+
*
|
|
199
|
+
* A `D` row contributes nothing. A rename contributes its NEW path (column 2),
|
|
200
|
+
* never its old one: the old path is a deletion and is parsed as such. Every
|
|
201
|
+
* other status (`M`, `A`, `C`, `T`, and the `U` of an unmerged path) has its
|
|
202
|
+
* path in column 1 and that path exists after the range.
|
|
203
|
+
*/
|
|
204
|
+
export function parseModifications(nameStatus: string): Set<string> {
|
|
205
|
+
const out = new Set<string>();
|
|
206
|
+
for (const line of nameStatus.split('\n')) {
|
|
207
|
+
const cols = line.split('\t');
|
|
208
|
+
const status = (cols[0] ?? '').trim();
|
|
209
|
+
if (status === '' || status === 'D') continue;
|
|
210
|
+
// `R<score>\told\tnew` / `C<score>\tsrc\tdest` — the surviving path is column 2.
|
|
211
|
+
const col = status.startsWith('R') || status.startsWith('C') ? cols[2] : cols[1];
|
|
212
|
+
if (col) out.add(col.trim());
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Names a finding points at: its `file`, plus every backticked token in the
|
|
219
|
+
* title and detail that could be a path or an artifact directory name.
|
|
220
|
+
*
|
|
221
|
+
* Backticks are the convention the review prompt asks for and the one every
|
|
222
|
+
* measured finding used, so this reads what the model actually writes rather
|
|
223
|
+
* than guessing at prose.
|
|
224
|
+
*/
|
|
225
|
+
export function namedArtifacts(f: Pick<Finding, 'title' | 'detail' | 'file'>): string[] {
|
|
226
|
+
const out = new Set<string>();
|
|
227
|
+
if (f.file) out.add(f.file);
|
|
228
|
+
for (const m of `${f.title}\n${f.detail}`.matchAll(/`([^`\n]{2,120})`/g)) {
|
|
229
|
+
const tok = (m[1] ?? '').trim();
|
|
230
|
+
// A token with whitespace is prose in backticks, not a name.
|
|
231
|
+
if (tok !== '' && !/\s/.test(tok)) out.add(tok);
|
|
232
|
+
}
|
|
233
|
+
return [...out];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Does the finding's text assert that something was removed from the tree? */
|
|
237
|
+
export function assertsDeletion(f: Pick<Finding, 'title' | 'detail'>): boolean {
|
|
238
|
+
return /\b(deleted|removed|deletion|removal)\b/i.test(`${f.title}\n${f.detail}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* The disproof, or null when there is none.
|
|
243
|
+
*
|
|
244
|
+
* WHY THIS IS MECHANICAL AND NOT A PROMPT LINE. The review runs over a
|
|
245
|
+
* per-file PARTITION of the span and, on a large release, over only part of it
|
|
246
|
+
* — 202 of 401 files at the 16.1.0 cut. A reviewer holding half the files
|
|
247
|
+
* cannot distinguish "this artifact is absent from my chunk" from "this
|
|
248
|
+
* artifact was deleted", and on that cut it reported two `critical security`
|
|
249
|
+
* deletions of skills the range never touched. Both blocked the release and
|
|
250
|
+
* cost a full adjudication cycle each.
|
|
251
|
+
*
|
|
252
|
+
* The prompt already carries a sentence for the neighbouring class ("Do NOT
|
|
253
|
+
* report a feature as not in the diff when it is present in the release
|
|
254
|
+
* range") and the same run violated it, which is the evidence that a sentence
|
|
255
|
+
* is not the control here.
|
|
256
|
+
*
|
|
257
|
+
* The test is conservative in the direction that matters: a disproof needs a
|
|
258
|
+
* named artifact that is BOTH absent from the range's deletion set AND present
|
|
259
|
+
* in the tree AND untouched by the range.
|
|
260
|
+
*
|
|
261
|
+
* THE THIRD HALF, AND WHY IT WAS MISSING. The first two were written against
|
|
262
|
+
* WHOLE-FILE deletion, where "a genuine deletion satisfies neither half" is
|
|
263
|
+
* true. It is false for a removal asserted INSIDE a file: "digest verification
|
|
264
|
+
* was removed in `install.ts`" names a path the range MODIFIED, so the path is
|
|
265
|
+
* absent from the deletion set and present in the tree, and the finding was
|
|
266
|
+
* de-blocked as though it had been checked. A path the range modified is
|
|
267
|
+
* evidence the range touched that artifact — the opposite of this disproof's
|
|
268
|
+
* premise — so it can no longer carry one.
|
|
269
|
+
*
|
|
270
|
+
* WHAT THIS GATE STILL CANNOT DECIDE, and says nothing about. The disproof is
|
|
271
|
+
* built entirely out of PATHS: the deletion set, the modification set, and the
|
|
272
|
+
* working tree. A claim about something that has no path of its own — a
|
|
273
|
+
* symbol, an export, a config key, a behaviour, a sentence inside a surviving
|
|
274
|
+
* file — cannot be resolved to a candidate, so no half of the test applies and
|
|
275
|
+
* the finding is returned unannotated. That is the intended reading: the gate
|
|
276
|
+
* cannot decide such a claim, it is not asserting the claim is true, and an
|
|
277
|
+
* operator adjudicating the block is reading an UNCHECKED finding rather than
|
|
278
|
+
* an unrefuted one. Widening the test to cover that class would mean deciding
|
|
279
|
+
* a claim from the same partition the reviewer had, which is the fabrication
|
|
280
|
+
* this mechanism exists to absorb.
|
|
281
|
+
*/
|
|
282
|
+
export function contradictedByTree(
|
|
283
|
+
f: Pick<Finding, 'title' | 'detail' | 'file'>,
|
|
284
|
+
deleted: ReadonlySet<string>,
|
|
285
|
+
exists: (p: string) => boolean,
|
|
286
|
+
modified: ReadonlySet<string> = new Set<string>(),
|
|
287
|
+
): string | null {
|
|
288
|
+
if (!assertsDeletion(f)) return null;
|
|
289
|
+
for (const name of namedArtifacts(f)) {
|
|
290
|
+
// Any deletion whose path mentions the token leaves the claim standing.
|
|
291
|
+
if ([...deleted].some((d) => d === name || d.includes(name))) continue;
|
|
292
|
+
const candidates = [name, `src/skills/${name}/SKILL.md`, `src/rules/${name}.md`, `src/scripts/${name}`];
|
|
293
|
+
// The range touched something this token resolves to — a whole-artifact
|
|
294
|
+
// disproof cannot be read off a path the range changed. Checked over
|
|
295
|
+
// EVERY candidate, not only the one `exists` happens to hit first: the
|
|
296
|
+
// conservative direction is fewer disproofs, never more.
|
|
297
|
+
if (candidates.some((c) => modified.has(c))) continue;
|
|
298
|
+
const hit = candidates.find((c) => exists(c));
|
|
299
|
+
if (hit !== undefined) {
|
|
300
|
+
return (
|
|
301
|
+
`the range deletes nothing matching \`${name}\`, and \`${hit}\` is present in the tree — ` +
|
|
302
|
+
'checked against `git diff --name-status base...HEAD` and the working tree, not against the review chunk.'
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The two counts road-to-a-fact-plane Step 2.3 pre-registers as its falsifier,
|
|
311
|
+
* written into the findings artifact so the next cut can be read rather than
|
|
312
|
+
* recomputed.
|
|
313
|
+
*
|
|
314
|
+
* `asserting_removal` is the population — findings that make a checkable
|
|
315
|
+
* factual claim of the deletion class. `disproved_by_tree` is how many of them
|
|
316
|
+
* the range and the tree refuted. The premise of the fact block is that
|
|
317
|
+
* supplying the range's facts lowers the first and, with it, the second; a
|
|
318
|
+
* `disproved_by_tree` that does not fall after the block ships is the honest
|
|
319
|
+
* null, and the roadmap records that result rather than widening the mechanism
|
|
320
|
+
* to chase it.
|
|
321
|
+
*
|
|
322
|
+
* Every recorded cut so far reports `disproved_by_tree` as 0 for a reason that
|
|
323
|
+
* is not a measurement: the annotation pass landed after the last of them, so
|
|
324
|
+
* the number was never produced. The baseline is "never observed", not
|
|
325
|
+
* "observed as zero", and the first cut after this change is the first reading.
|
|
326
|
+
*/
|
|
327
|
+
export function factClaimCounts(findings: readonly Finding[]): {
|
|
328
|
+
asserting_removal: number;
|
|
329
|
+
disproved_by_tree: number;
|
|
330
|
+
} {
|
|
331
|
+
return {
|
|
332
|
+
asserting_removal: findings.filter((f) => assertsDeletion(f)).length,
|
|
333
|
+
disproved_by_tree: findings.filter((f) => (f.contradicted ?? '').trim() !== '').length,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Render one list of the fact block, or an explicit `(none)`. */
|
|
338
|
+
function factList(label: string, paths: readonly string[]): string {
|
|
339
|
+
const head = `${label} — ${String(paths.length)}:`;
|
|
340
|
+
return paths.length === 0 ? `${head}\n (none)` : `${head}\n${paths.map((p) => ` ${p}`).join('\n')}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The range's facts, serialised for the reviewer.
|
|
345
|
+
*
|
|
346
|
+
* WHY THE REVIEWER IS HANDED THESE RATHER THAN LEFT TO INFER THEM. The review
|
|
347
|
+
* runs over a per-file PARTITION of the span and, on a large release, over only
|
|
348
|
+
* part of it — 202 of 401 files at the 16.1.0 cut. From inside one chunk, "this
|
|
349
|
+
* path is not in front of me" and "this path was deleted" are the same
|
|
350
|
+
* observation, and on that cut the model resolved the ambiguity the wrong way
|
|
351
|
+
* twice, in `critical security` findings that each cost an adjudication cycle.
|
|
352
|
+
* `contradictedByTree` catches that class AFTER the spend; this block removes
|
|
353
|
+
* the inference that produces it.
|
|
354
|
+
*
|
|
355
|
+
* Exactly three lists, all of them already computed by this gate: the paths the
|
|
356
|
+
* range deleted, the paths it changed and left standing, and the working tree's
|
|
357
|
+
* presence check over both — reported by its EXCEPTIONS, because the normal
|
|
358
|
+
* case is empty and a third full copy of the same paths would spend budget on
|
|
359
|
+
* a line the reviewer can derive. Nothing else is added: the block is paths, so
|
|
360
|
+
* it scales with file count and not with change size, and the six-request
|
|
361
|
+
* ceiling stays a budget question this does not reopen.
|
|
362
|
+
*/
|
|
363
|
+
export function factBlock(
|
|
364
|
+
deleted: ReadonlySet<string>,
|
|
365
|
+
modified: ReadonlySet<string>,
|
|
366
|
+
exists: (p: string) => boolean,
|
|
367
|
+
): string {
|
|
368
|
+
const del = [...deleted].sort();
|
|
369
|
+
const mod = [...modified].sort();
|
|
370
|
+
return [
|
|
371
|
+
'=== RANGE FACTS (AUTHORITATIVE) ===',
|
|
372
|
+
'Read from `git diff --name-status <base>...HEAD` and from the working tree,',
|
|
373
|
+
'NOT from the diff below. The diff below is ONE PARTITION of this range: a',
|
|
374
|
+
'path missing from it is a path outside your chunk, never a deleted path.',
|
|
375
|
+
'A claim that contradicts these lists is a defect in the finding, not a',
|
|
376
|
+
'finding about the tree — do not report it.',
|
|
377
|
+
'',
|
|
378
|
+
factList('DELETED BY THIS RANGE', del),
|
|
379
|
+
'',
|
|
380
|
+
factList('CHANGED BY THIS RANGE, AND THE TREE HOLDS THEM', mod),
|
|
381
|
+
'',
|
|
382
|
+
'PRESENCE CHECK (exceptions to the two lists above):',
|
|
383
|
+
factList(' deleted, yet still present in the working tree', del.filter((p) => exists(p))),
|
|
384
|
+
factList(' changed, yet absent from the working tree', mod.filter((p) => !exists(p))),
|
|
385
|
+
'=== END RANGE FACTS ===',
|
|
386
|
+
'',
|
|
387
|
+
'',
|
|
388
|
+
].join('\n');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Annotate every finding the tree disproves. Pure given `deleted` + `exists`. */
|
|
392
|
+
export function annotateContradicted(
|
|
393
|
+
findings: readonly Finding[],
|
|
394
|
+
deleted: ReadonlySet<string>,
|
|
395
|
+
exists: (p: string) => boolean,
|
|
396
|
+
modified: ReadonlySet<string> = new Set<string>(),
|
|
397
|
+
): Finding[] {
|
|
398
|
+
return findings.map((f) => {
|
|
399
|
+
const why = contradictedByTree(f, deleted, exists, modified);
|
|
400
|
+
return why === null ? f : { ...f, contradicted: why };
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
123
404
|
function diffText(baseRef: string, files: string[], cwd: string = REPO_ROOT): string {
|
|
124
405
|
if (files.length === 0) return '';
|
|
125
406
|
const r = spawnSync('git', ['diff', `${baseRef}...HEAD`, '--', ...files], {
|
|
@@ -459,6 +740,14 @@ export interface ReviewPlan {
|
|
|
459
740
|
* it thrown away.
|
|
460
741
|
*/
|
|
461
742
|
partition: DiffPartition;
|
|
743
|
+
/**
|
|
744
|
+
* The range facts prepended to EVERY request — see `factBlock`.
|
|
745
|
+
*
|
|
746
|
+
* Computed in the plan rather than at send time so `--dry-run` can print
|
|
747
|
+
* the request the live run would send, and so the partition budget can
|
|
748
|
+
* reserve the block's length instead of discovering it after the split.
|
|
749
|
+
*/
|
|
750
|
+
facts: string;
|
|
462
751
|
/** Model calls the live run will make — one per budgeted chunk. */
|
|
463
752
|
requests: number;
|
|
464
753
|
/** Files no request will carry, with the reason. */
|
|
@@ -499,22 +788,33 @@ export function buildPlan(baseRef: string, cwd: string = REPO_ROOT): ReviewPlan
|
|
|
499
788
|
// REQUEST COUNT rather than only a token estimate. This gate spends money
|
|
500
789
|
// per request, and a plan that reports "~N tokens" without saying how many
|
|
501
790
|
// calls that becomes hides the number the maintainer is deciding about.
|
|
502
|
-
|
|
791
|
+
// The facts ride on every request, so the per-request diff budget has to
|
|
792
|
+
// pay for them. Reserving here rather than at send time is what keeps the
|
|
793
|
+
// block from silently pushing a chunk over the provider's cap — the exact
|
|
794
|
+
// failure four consecutive releases already spent a review on.
|
|
795
|
+
const facts = factBlock(rangeDeletions(analysisBase, cwd), rangeModifications(analysisBase, cwd), (p) =>
|
|
796
|
+
existsSync(path.join(cwd, p)),
|
|
797
|
+
);
|
|
798
|
+
const partition = partitionDiff(
|
|
799
|
+
perFileDiffs(analysisBase, files, cwd),
|
|
800
|
+
Math.max(1, PROMPT_BUDGET_CHARS - facts.length),
|
|
801
|
+
);
|
|
503
802
|
return {
|
|
504
803
|
partition,
|
|
804
|
+
facts,
|
|
505
805
|
requests: partition.chunks.length,
|
|
506
806
|
unreviewed: partition.unreviewed,
|
|
507
807
|
skills: [...REVIEW_SKILLS],
|
|
508
808
|
files,
|
|
509
809
|
analysisBase,
|
|
510
810
|
...(release ? { release } : {}),
|
|
511
|
-
promptChars: systemPrompt.length + diff.length,
|
|
811
|
+
promptChars: systemPrompt.length + facts.length + diff.length,
|
|
512
812
|
escalation,
|
|
513
813
|
note:
|
|
514
814
|
files.length === 0
|
|
515
815
|
? 'No reviewable (non-generated) files changed — the live review would no-op.'
|
|
516
816
|
: `${files.length} reviewable file(s); live review would send ~${Math.ceil(
|
|
517
|
-
(systemPrompt.length * Math.max(1, partition.chunks.length) + diff.length) /
|
|
817
|
+
((systemPrompt.length + facts.length) * Math.max(1, partition.chunks.length) + diff.length) /
|
|
518
818
|
BUDGET_CHARS_PER_TOKEN,
|
|
519
819
|
)} input tokens across ${partition.chunks.length} request(s)` +
|
|
520
820
|
(partition.unreviewed.length > 0
|
|
@@ -639,7 +939,11 @@ export function renderReview(findings: Finding[], enforce: boolean, escalation:
|
|
|
639
939
|
// read as inconsistent with the narrow `blocking.length` verdict count.
|
|
640
940
|
const rows = findings
|
|
641
941
|
.map((f) => {
|
|
642
|
-
const marker =
|
|
942
|
+
const marker = (f.contradicted ?? '') !== ''
|
|
943
|
+
? 'Contradicted'
|
|
944
|
+
: classifyBlocking(f)
|
|
945
|
+
? 'Blocking'
|
|
946
|
+
: 'Advisory';
|
|
643
947
|
return `| ${findingId(f)} | ${f.severity} (${marker}) | ${f.kind} | ${f.file ?? '—'} | ${f.title} |`;
|
|
644
948
|
})
|
|
645
949
|
.join('\n');
|
|
@@ -653,6 +957,12 @@ export function renderReview(findings: Finding[], enforce: boolean, escalation:
|
|
|
653
957
|
// 12-hex id looks exactly like a short SHA, so the rendered comment now says
|
|
654
958
|
// what it is and where its disposition lives. This is the only place a reader
|
|
655
959
|
// of the comment can learn it without opening the source.
|
|
960
|
+
const contradicted = findings.filter((f) => (f.contradicted ?? '') !== '');
|
|
961
|
+
const contradictedNote = contradicted.length === 0
|
|
962
|
+
? ''
|
|
963
|
+
: '\n\n**Contradicted by the tree** — these assert a removal the range does not contain, '
|
|
964
|
+
+ 'so they are reported and NOT counted as blocking:\n'
|
|
965
|
+
+ contradicted.map((f) => `- \`${findingId(f)}\` ${f.title} — ${f.contradicted ?? ''}`).join('\n');
|
|
656
966
|
const idNote =
|
|
657
967
|
'\n\n`id` above is a FINDING id — the first 12 hex of sha256(kind|title|file), '
|
|
658
968
|
+ '**not a commit SHA**. Searching the git log for it finds nothing, by design. '
|
|
@@ -662,9 +972,20 @@ export function renderReview(findings: Finding[], enforce: boolean, escalation:
|
|
|
662
972
|
// demands committed dispositions. The comment is transport, never the
|
|
663
973
|
// record — the record is `agents/evidence/release-findings/<version>.json`.
|
|
664
974
|
const machine = `\n\n<!-- release-findings-json: ${JSON.stringify(
|
|
665
|
-
findings.map((f) => ({
|
|
975
|
+
findings.map((f) => ({
|
|
976
|
+
finding_id: findingId(f),
|
|
977
|
+
severity: f.severity,
|
|
978
|
+
kind: f.kind,
|
|
979
|
+
title: f.title,
|
|
980
|
+
file: f.file ?? null,
|
|
981
|
+
// Carried into the trigger block, not only the rendered table: the
|
|
982
|
+
// PR-comment path of `check_finding_dispositions` reads THIS, and
|
|
983
|
+
// would otherwise demand a disposition for a claim the tree already
|
|
984
|
+
// disproved.
|
|
985
|
+
...((f.contradicted ?? '') !== '' ? { contradicted: f.contradicted } : {}),
|
|
986
|
+
})),
|
|
666
987
|
)} -->`;
|
|
667
|
-
return `${banner}\n\n${verdictLine}\n\n| id | severity | kind | file | finding |\n|---|---|---|---|---|\n${rows}${idNote}${cov}${esc}${machine}`;
|
|
988
|
+
return `${banner}\n\n${verdictLine}\n\n| id | severity | kind | file | finding |\n|---|---|---|---|---|\n${rows}${contradictedNote}${idNote}${cov}${esc}${machine}`;
|
|
668
989
|
}
|
|
669
990
|
|
|
670
991
|
function postReview(body: string): void {
|
|
@@ -700,6 +1021,16 @@ export function main(argv: string[]): 0 | 2 {
|
|
|
700
1021
|
|
|
701
1022
|
const plan = buildPlan(baseRef);
|
|
702
1023
|
|
|
1024
|
+
// The request the live run would send, verbatim and unspent. A summary
|
|
1025
|
+
// would be this script's description of the request rather than the
|
|
1026
|
+
// request, and the whole point of the fact block is that what the reviewer
|
|
1027
|
+
// is handed can be read rather than inferred — including by a maintainer.
|
|
1028
|
+
if (argv.includes('--print-request')) {
|
|
1029
|
+
const first = plan.partition.chunks[0];
|
|
1030
|
+
process.stdout.write(plan.facts + (first?.text ?? '(no chunk — nothing to review in this range)\n'));
|
|
1031
|
+
return 0;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
703
1034
|
if (dryRun) {
|
|
704
1035
|
process.stdout.write(
|
|
705
1036
|
`self-review-gate (dry-run — no spend, advisory):\n` +
|
|
@@ -812,7 +1143,10 @@ export function main(argv: string[]): 0 | 2 {
|
|
|
812
1143
|
let reviewedChunks = 0;
|
|
813
1144
|
let filesRead = 0;
|
|
814
1145
|
for (const [i, chunk] of partition.chunks.entries()) {
|
|
815
|
-
|
|
1146
|
+
// Facts FIRST, then the partition. The reviewer reads what the
|
|
1147
|
+
// range did before it reads the fragment it was given, so "absent
|
|
1148
|
+
// from my chunk" can no longer be mistaken for "deleted".
|
|
1149
|
+
const resp = client.ask(systemPrompt, plan.facts + chunk.text, 4096);
|
|
816
1150
|
if (resp.error) {
|
|
817
1151
|
process.stdout.write(
|
|
818
1152
|
`::warning::self-review-gate — chunk ${String(i + 1)}/` +
|
|
@@ -854,6 +1188,16 @@ export function main(argv: string[]): 0 | 2 {
|
|
|
854
1188
|
filesTotal: plan.files.length,
|
|
855
1189
|
unreviewed,
|
|
856
1190
|
};
|
|
1191
|
+
// Ground-truth pass, ONCE, before anything reads the finding set. A
|
|
1192
|
+
// deletion claim the range and the tree both disprove is annotated
|
|
1193
|
+
// here and stops being merge-blocking; it is still written to the
|
|
1194
|
+
// artifact and still rendered, carrying its disproof.
|
|
1195
|
+
const reviewed = annotateContradicted(
|
|
1196
|
+
dedupeFindings(findings),
|
|
1197
|
+
rangeDeletions(baseRef),
|
|
1198
|
+
(p) => existsSync(path.join(REPO_ROOT, p)),
|
|
1199
|
+
rangeModifications(baseRef),
|
|
1200
|
+
);
|
|
857
1201
|
const outIdx = argv.indexOf('--findings-out');
|
|
858
1202
|
if (outIdx >= 0 && argv[outIdx + 1]) {
|
|
859
1203
|
// Durable ingestion input for the disposition ledger (release-truth
|
|
@@ -869,7 +1213,12 @@ export function main(argv: string[]): 0 | 2 {
|
|
|
869
1213
|
schema_version: 1,
|
|
870
1214
|
...independenceFields(['anthropic']),
|
|
871
1215
|
coverage,
|
|
872
|
-
|
|
1216
|
+
// Readable, not recomputable: the falsifier for the
|
|
1217
|
+
// supplied-facts change is a count over the NEXT cut,
|
|
1218
|
+
// and a number a reader has to re-derive from 50
|
|
1219
|
+
// findings is a number nobody checks.
|
|
1220
|
+
fact_claims: factClaimCounts(reviewed),
|
|
1221
|
+
findings: reviewed.map((f) => ({
|
|
873
1222
|
finding_id: findingId(f),
|
|
874
1223
|
...f,
|
|
875
1224
|
})),
|
|
@@ -879,10 +1228,10 @@ export function main(argv: string[]): 0 | 2 {
|
|
|
879
1228
|
) + '\n',
|
|
880
1229
|
);
|
|
881
1230
|
}
|
|
882
|
-
const body = renderReview(
|
|
1231
|
+
const body = renderReview(reviewed, enforce, plan.escalation, coverage);
|
|
883
1232
|
postReview(body);
|
|
884
1233
|
|
|
885
|
-
const code = gateVerdict(
|
|
1234
|
+
const code = gateVerdict(reviewed, { enforce });
|
|
886
1235
|
if (code === 2) {
|
|
887
1236
|
process.stdout.write('::error::self-review-gate — merge-blocking findings under enforce mode.\n');
|
|
888
1237
|
}
|