@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
|
@@ -103,6 +103,121 @@ export interface Ledger {
|
|
|
103
103
|
findings: LedgerFinding[];
|
|
104
104
|
/** Why the finding set is empty. Required when it is; see empty_ledger_problem. */
|
|
105
105
|
no_findings_reason?: string;
|
|
106
|
+
// The six integrity fields, declared rather than swept into an index
|
|
107
|
+
// signature. An index signature would hold them, and would also disable
|
|
108
|
+
// excess-property and typo detection on the whole type for every consumer:
|
|
109
|
+
// under `--strict`, `l.findigs` type-checks against a Ledger carrying one.
|
|
110
|
+
// The set is known and finite, so it costs six lines to keep the type.
|
|
111
|
+
review_independence?: string;
|
|
112
|
+
context_relation?: string;
|
|
113
|
+
acceptance_status?: string;
|
|
114
|
+
assurance?: string;
|
|
115
|
+
reviewers?: string[];
|
|
116
|
+
coverage?: unknown;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Fields the self-review artifact carries that say how much a ledger's silence
|
|
121
|
+
* is worth, and which `--ingest` used to drop on the floor.
|
|
122
|
+
*
|
|
123
|
+
* `check_review_schema` derives `acceptance_status` and `assurance` from
|
|
124
|
+
* `review_independence` and fails a ledger whose declarations disagree — so an
|
|
125
|
+
* ingested ledger missing all three was not merely thinner than a hand-written
|
|
126
|
+
* one, it could not satisfy the gate that reads it. Every committed ledger from
|
|
127
|
+
* 14.19.0 onward carries the full set; only the ingest path produced files
|
|
128
|
+
* without it, which is one of the two reasons nobody could run the ingest and
|
|
129
|
+
* get a green tree.
|
|
130
|
+
*
|
|
131
|
+
* Carried rather than recomputed: the producing run is the only thing that
|
|
132
|
+
* knows how many reviewers answered and how much of the diff they saw, and a
|
|
133
|
+
* consumer inferring it from the findings would be inventing the integrity
|
|
134
|
+
* claim the fields exist to record.
|
|
135
|
+
*/
|
|
136
|
+
export const INTEGRITY_FIELDS = [
|
|
137
|
+
'review_independence',
|
|
138
|
+
'context_relation',
|
|
139
|
+
'acceptance_status',
|
|
140
|
+
'assurance',
|
|
141
|
+
'reviewers',
|
|
142
|
+
'coverage',
|
|
143
|
+
] as const;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Merge a self-review artifact into a ledger: new findings by id, the integrity
|
|
147
|
+
* fields on first write, and — when the review found nothing — the reason that
|
|
148
|
+
* makes an empty ledger legible.
|
|
149
|
+
*
|
|
150
|
+
* MUTATES AND RETURNS THE SAME OBJECT. Callers must use the returned `ledger`
|
|
151
|
+
* rather than relying on the mutation; the two are the same reference today and
|
|
152
|
+
* the signature is what a later reader will trust.
|
|
153
|
+
*
|
|
154
|
+
* The integrity fields are written only when the ledger does not already carry
|
|
155
|
+
* them. A second ingest into a ledger a human has since adjudicated must not
|
|
156
|
+
* silently restate what the earlier run claimed about independence — the first
|
|
157
|
+
* producing run is the one whose claim the dispositions were filled against.
|
|
158
|
+
*
|
|
159
|
+
* The empty-findings case is the one that used to deadlock a release. A clean
|
|
160
|
+
* review produces `findings: []`, `empty_ledger_problem` refuses an empty
|
|
161
|
+
* ledger with no `no_findings_reason`, and the release then stopped demanding
|
|
162
|
+
* dispositions for zero findings — an instruction nobody can follow. An ingest
|
|
163
|
+
* knows exactly what a clean review means and writes it, with the coverage
|
|
164
|
+
* numbers as the checkable part rather than a bare assurance.
|
|
165
|
+
*/
|
|
166
|
+
export function merge_ingest(
|
|
167
|
+
ledger: Ledger,
|
|
168
|
+
artifact: Record<string, unknown>,
|
|
169
|
+
): { ledger: Ledger; added: number; carried: string[]; reasoned: boolean } {
|
|
170
|
+
const known = new Set(ledger.findings.map((f) => f.finding_id));
|
|
171
|
+
let added = 0;
|
|
172
|
+
for (const f of (artifact['findings'] as LedgerFinding[] | undefined) ?? []) {
|
|
173
|
+
if (!known.has(f.finding_id)) {
|
|
174
|
+
ledger.findings.push(f);
|
|
175
|
+
added++;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Assigned through one `unknown` view rather than per field: the six are a
|
|
179
|
+
// closed set the type declares, and spelling out six near-identical
|
|
180
|
+
// if-blocks to satisfy the checker would make the set harder to extend than
|
|
181
|
+
// the type is to read. The view is local to this loop.
|
|
182
|
+
const carried: string[] = [];
|
|
183
|
+
const sink = ledger as unknown as Record<string, unknown>;
|
|
184
|
+
for (const key of INTEGRITY_FIELDS) {
|
|
185
|
+
if (sink[key] === undefined && artifact[key] !== undefined) {
|
|
186
|
+
sink[key] = artifact[key];
|
|
187
|
+
carried.push(key);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Only on an artifact that actually SAYS it found nothing. Without the key
|
|
191
|
+
// check, any JSON object without a `findings` key reads as an empty finding
|
|
192
|
+
// set, and the sentence below would assert a review that never happened —
|
|
193
|
+
// into the permanent record, which is worse than the deadlock it replaces.
|
|
194
|
+
let reasoned = false;
|
|
195
|
+
const declaresFindings = Array.isArray(artifact['findings']);
|
|
196
|
+
if (
|
|
197
|
+
declaresFindings &&
|
|
198
|
+
ledger.findings.length === 0 &&
|
|
199
|
+
(ledger.no_findings_reason ?? '').trim() === ''
|
|
200
|
+
) {
|
|
201
|
+
ledger.no_findings_reason = _clean_review_reason(artifact);
|
|
202
|
+
reasoned = true;
|
|
203
|
+
}
|
|
204
|
+
return { ledger, added, carried, reasoned };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The `no_findings_reason` an ingest of a zero-findings artifact writes. */
|
|
208
|
+
function _clean_review_reason(artifact: Record<string, unknown>): string {
|
|
209
|
+
const cov = artifact['coverage'] as Record<string, unknown> | undefined;
|
|
210
|
+
const reviewed = cov?.['filesReviewed'];
|
|
211
|
+
const total = cov?.['filesTotal'];
|
|
212
|
+
const scope =
|
|
213
|
+
typeof reviewed === 'number' && typeof total === 'number'
|
|
214
|
+
? ` over ${reviewed} of ${total} changed file(s)`
|
|
215
|
+
: '';
|
|
216
|
+
return (
|
|
217
|
+
`The self-review ran and reported no findings${scope}; ingested from its own ` +
|
|
218
|
+
'artifact by the release, so the empty set is a result rather than an absence. ' +
|
|
219
|
+
'Check it against the coverage block above and the run that produced it.'
|
|
220
|
+
);
|
|
106
221
|
}
|
|
107
222
|
|
|
108
223
|
/** Mirrors self_review_gate.classifyBlocking — security/claim × critical/high. */
|
|
@@ -389,32 +504,68 @@ function main(argv: readonly string[]): number {
|
|
|
389
504
|
const ledgerPath = _ledger_path(dir, release);
|
|
390
505
|
|
|
391
506
|
if (ingest) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
507
|
+
// Guarded: a truncated or corrupt artifact otherwise exits the child on
|
|
508
|
+
// an uncaught exception, and the release reports only "command failed"
|
|
509
|
+
// with no indication that the artifact is the problem.
|
|
510
|
+
let artifact: Record<string, unknown>;
|
|
511
|
+
try {
|
|
512
|
+
artifact = JSON.parse(fs.readFileSync(ingest, 'utf-8')) as Record<string, unknown>;
|
|
513
|
+
} catch (e) {
|
|
514
|
+
process.stderr.write(
|
|
515
|
+
`❌ ${ingest}: not readable as JSON (${e instanceof Error ? e.message : String(e)})\n`,
|
|
516
|
+
);
|
|
517
|
+
return 2;
|
|
518
|
+
}
|
|
519
|
+
// Run the incoming findings through the parser the committed ledger goes
|
|
520
|
+
// through, and keep the RESULT rather than discarding it. What that buys
|
|
521
|
+
// is narrow and worth stating: the parser checks the envelope and that
|
|
522
|
+
// every entry has a non-empty `finding_id`. It does NOT check severity,
|
|
523
|
+
// kind or title, so an entry with a bad severity still lands — the
|
|
524
|
+
// schema gate is what reads those, on the committed file.
|
|
525
|
+
let incoming: Ledger;
|
|
526
|
+
try {
|
|
527
|
+
incoming = parse_ledger(
|
|
528
|
+
JSON.stringify({
|
|
529
|
+
schema_version: 1,
|
|
530
|
+
release,
|
|
531
|
+
findings: (artifact['findings'] as LedgerFinding[] | undefined) ?? [],
|
|
532
|
+
}),
|
|
533
|
+
ingest,
|
|
534
|
+
);
|
|
535
|
+
} catch (e) {
|
|
536
|
+
process.stderr.write(`❌ ${e instanceof Error ? e.message : String(e)}\n`);
|
|
537
|
+
return 2;
|
|
538
|
+
}
|
|
400
539
|
let ledger: Ledger = { schema_version: 1, release, findings: [] };
|
|
401
540
|
if (fs.existsSync(ledgerPath)) {
|
|
402
541
|
ledger = parse_ledger(fs.readFileSync(ledgerPath, 'utf-8'), ledgerPath);
|
|
403
542
|
}
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (!known.has(f.finding_id)) {
|
|
408
|
-
ledger.findings.push(f);
|
|
409
|
-
added++;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
543
|
+
const merged = merge_ingest(ledger, { ...artifact, findings: incoming.findings });
|
|
544
|
+
ledger = merged.ledger;
|
|
545
|
+
const { added, carried, reasoned } = merged;
|
|
412
546
|
fs.mkdirSync(path.dirname(ledgerPath), { recursive: true });
|
|
413
547
|
fs.writeFileSync(ledgerPath, JSON.stringify(ledger, null, 2) + '\n');
|
|
414
548
|
process.stdout.write(
|
|
415
549
|
`📥 ingested ${added} new finding(s) into ${path.relative(REPO_ROOT, ledgerPath)} — ` +
|
|
416
550
|
'fill dispositions before the release validation goes green\n',
|
|
417
551
|
);
|
|
552
|
+
if (carried.length > 0) {
|
|
553
|
+
process.stdout.write(` carried integrity fields: ${carried.join(', ')}\n`);
|
|
554
|
+
}
|
|
555
|
+
if (reasoned) {
|
|
556
|
+
process.stdout.write(
|
|
557
|
+
' the review reported nothing — wrote no_findings_reason so the empty ' +
|
|
558
|
+
'ledger is a result rather than an absence\n',
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
const absent = INTEGRITY_FIELDS.filter((k) => ledger[k] === undefined);
|
|
562
|
+
if (absent.length > 0) {
|
|
563
|
+
process.stdout.write(
|
|
564
|
+
`⚠️ artifact carried no ${absent.join(', ')} — ` +
|
|
565
|
+
'check_review_schema derives acceptance_status and assurance from ' +
|
|
566
|
+
'review_independence and will red on a ledger that declares neither\n',
|
|
567
|
+
);
|
|
568
|
+
}
|
|
418
569
|
return 0;
|
|
419
570
|
}
|
|
420
571
|
|
|
@@ -36,6 +36,12 @@ import * as os from 'node:os';
|
|
|
36
36
|
|
|
37
37
|
import { reportScanned } from './_lib/scan_scope.js';
|
|
38
38
|
import { runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
|
|
39
|
+
import {
|
|
40
|
+
INVENTORY_REL,
|
|
41
|
+
inventoryVerdict,
|
|
42
|
+
resetSourceCache,
|
|
43
|
+
type InventoryVerdict,
|
|
44
|
+
} from './_lib/loop_surfaces.js';
|
|
39
45
|
|
|
40
46
|
const REPO_ROOT = process.cwd();
|
|
41
47
|
const TASKFILES_DIR = 'taskfiles';
|
|
@@ -238,6 +244,23 @@ export interface GateVerdict {
|
|
|
238
244
|
unreasoned: string[];
|
|
239
245
|
/** Recorded exemptions that are no longer unreachable — stale rows. */
|
|
240
246
|
stale: string[];
|
|
247
|
+
/**
|
|
248
|
+
* The `_lib-export-reach` axis: a declared loop instrument nothing calls.
|
|
249
|
+
*
|
|
250
|
+
* A second axis in this one script rather than a second gate, because it
|
|
251
|
+
* asks the same question one layer down. The first axis asks whether CI
|
|
252
|
+
* reaches a gate TARGET; this asks whether production code reaches a
|
|
253
|
+
* declared INSTRUMENT. A separate gate would need its own registration,
|
|
254
|
+
* its own coverage row and its own wiring — and would then be the thing
|
|
255
|
+
* this family exists to detect if any of that were missed.
|
|
256
|
+
*
|
|
257
|
+
* Deliberately scoped to the instruments named in `loop-surfaces.yaml`
|
|
258
|
+
* rather than to every `_lib` export. Measured on `loop_guards.ts`, the
|
|
259
|
+
* unscoped version reports nine of fourteen exports dead; a gate whose
|
|
260
|
+
* signal is a minority of its own output gets allowlisted rather than
|
|
261
|
+
* answered.
|
|
262
|
+
*/
|
|
263
|
+
inventory: InventoryVerdict;
|
|
241
264
|
}
|
|
242
265
|
|
|
243
266
|
/**
|
|
@@ -255,25 +278,51 @@ export function gateVerdict(root = REPO_ROOT): GateVerdict {
|
|
|
255
278
|
return {
|
|
256
279
|
unreasoned: r.unreachable.filter((n) => !exempt.has(n)).sort(),
|
|
257
280
|
stale: [...exempt.keys()].filter((n) => !unreachable.has(n)).sort(),
|
|
281
|
+
inventory: inventoryVerdict(root),
|
|
258
282
|
};
|
|
259
283
|
}
|
|
260
284
|
|
|
285
|
+
/** The `_lib-export-reach` half of the verdict, written to stderr. */
|
|
286
|
+
function reportInventory(inv: InventoryVerdict): void {
|
|
287
|
+
for (const f of inv.shape) {
|
|
288
|
+
process.stderr.write(
|
|
289
|
+
`❌ ${INVENTORY_REL}: surface \`${f.id}\` — ${f.detail} (${f.reason}). Every surface ` +
|
|
290
|
+
'carries every field, and a bound resolves to something that exists.\n',
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
for (const f of inv.reach) {
|
|
294
|
+
const fix =
|
|
295
|
+
f.reason === 'dead'
|
|
296
|
+
? 'Give it a production consumer, declare it `status: experimental` with a future ' +
|
|
297
|
+
'`expires:`, or delete it. A built instrument nothing calls is not built.'
|
|
298
|
+
: 'An exemption is a dated deferral, not a label. Decide it, or move the date and ' +
|
|
299
|
+
'say why in `note:`.';
|
|
300
|
+
process.stderr.write(`❌ ${f.id} (${f.file}) — ${f.detail}. ${fix}\n`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
261
304
|
function runGate(root: string): number {
|
|
262
305
|
const v = gateVerdict(root);
|
|
263
306
|
const exempt = readExemptions(root);
|
|
307
|
+
const inv = v.inventory;
|
|
308
|
+
const declared = inv.live.length + inv.exempt.length + inv.reach.length;
|
|
264
309
|
reportScanned({
|
|
265
310
|
gate: 'check_gate_reachability',
|
|
266
|
-
scanned: exempt.size + analyse(root).unreachable.length,
|
|
267
|
-
units: 'exemption row(s) + unreachable target(s)',
|
|
268
|
-
roots: [EXEMPTIONS_REL, ROOT_TASKFILE, TASKFILES_DIR],
|
|
311
|
+
scanned: exempt.size + analyse(root).unreachable.length + declared,
|
|
312
|
+
units: 'exemption row(s) + unreachable target(s) + declared loop instrument(s)',
|
|
313
|
+
roots: [EXEMPTIONS_REL, INVENTORY_REL, ROOT_TASKFILE, TASKFILES_DIR],
|
|
269
314
|
});
|
|
270
|
-
|
|
315
|
+
const inventoryClean = inv.shape.length === 0 && inv.reach.length === 0;
|
|
316
|
+
if (v.unreasoned.length === 0 && v.stale.length === 0 && inventoryClean) {
|
|
271
317
|
process.stdout.write(
|
|
272
318
|
`✅ check_gate_reachability: every unreachable gate target carries a recorded reason ` +
|
|
273
|
-
`(${String(exempt.size)} exempt)
|
|
319
|
+
`(${String(exempt.size)} exempt); every declared loop instrument is called or ` +
|
|
320
|
+
`dated (${String(inv.live.length)} live, ${String(inv.exempt.length)} experimental` +
|
|
321
|
+
`${inv.proseBound.length > 0 ? `, ${String(inv.proseBound.length)} prose-bound surface(s)` : ''}).\n`,
|
|
274
322
|
);
|
|
275
323
|
return 0;
|
|
276
324
|
}
|
|
325
|
+
reportInventory(inv);
|
|
277
326
|
for (const n of v.unreasoned) {
|
|
278
327
|
process.stderr.write(
|
|
279
328
|
`❌ ${n} is gate-shaped, unreachable from \`task ci\` and every workflow, and carries ` +
|
|
@@ -320,6 +369,58 @@ function selfTest(): number {
|
|
|
320
369
|
return v.unreasoned.length === 0 && v.stale.length === 0 ? 0 : 1;
|
|
321
370
|
};
|
|
322
371
|
|
|
372
|
+
/**
|
|
373
|
+
* A throwaway tree for the `_lib-export-reach` axis.
|
|
374
|
+
*
|
|
375
|
+
* `surface` and `instrument` are written verbatim into the inventory, so a
|
|
376
|
+
* case can plant a malformed row without the builder silently repairing
|
|
377
|
+
* it — which is the whole point of a planted failure.
|
|
378
|
+
*/
|
|
379
|
+
const inventoryFixture = (opts: { surface: string; instrument: string; calls: boolean }): number => {
|
|
380
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gr-inv-'));
|
|
381
|
+
roots.push(dir);
|
|
382
|
+
fs.mkdirSync(path.join(dir, 'src', 'config'), { recursive: true });
|
|
383
|
+
fs.mkdirSync(path.join(dir, 'src', 'lib'), { recursive: true });
|
|
384
|
+
fs.writeFileSync(
|
|
385
|
+
path.join(dir, 'src', 'lib', 'thing.ts'),
|
|
386
|
+
[
|
|
387
|
+
'export const CEILING = 3;',
|
|
388
|
+
'export function stalled(): boolean { return false; }',
|
|
389
|
+
'export function helper(): number { return CEILING; }',
|
|
390
|
+
...(opts.calls ? ['export const used = helper();'] : []),
|
|
391
|
+
].join('\n'),
|
|
392
|
+
'utf-8',
|
|
393
|
+
);
|
|
394
|
+
fs.writeFileSync(
|
|
395
|
+
path.join(dir, 'src', 'config', 'loop-surfaces.yaml'),
|
|
396
|
+
`surfaces:\n${opts.surface}\ninstruments:\n${opts.instrument}\n`,
|
|
397
|
+
'utf-8',
|
|
398
|
+
);
|
|
399
|
+
resetSourceCache();
|
|
400
|
+
const v = inventoryVerdict(dir);
|
|
401
|
+
resetSourceCache();
|
|
402
|
+
return v.shape.length === 0 && v.reach.length === 0 ? 0 : 1;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const wholeSurface = [
|
|
406
|
+
' s:',
|
|
407
|
+
' kind: code',
|
|
408
|
+
' bound_kind: code',
|
|
409
|
+
' cap: src/lib/thing.ts:CEILING',
|
|
410
|
+
' no_progress: src/lib/thing.ts:stalled',
|
|
411
|
+
' success_stop: a stated stop',
|
|
412
|
+
' checker: a stated checker',
|
|
413
|
+
' human_gate: none',
|
|
414
|
+
' terminal_vocabulary: none',
|
|
415
|
+
' production_consumers: [src/lib/thing.ts]',
|
|
416
|
+
].join('\n');
|
|
417
|
+
// Identical but for the one missing field — so a red can only be the cap.
|
|
418
|
+
const cappedSurface = wholeSurface
|
|
419
|
+
.split('\n')
|
|
420
|
+
.filter((l) => !l.includes('cap:'))
|
|
421
|
+
.join('\n');
|
|
422
|
+
const plainInstrument = [' helper:', ' file: src/lib/thing.ts', ' role: a stated role'].join('\n');
|
|
423
|
+
|
|
323
424
|
const cases: SelfTestCase[] = [
|
|
324
425
|
{
|
|
325
426
|
name: 'a wired gate target → accept',
|
|
@@ -343,9 +444,63 @@ function selfTest(): number {
|
|
|
343
444
|
expect: 'reject',
|
|
344
445
|
run: () => fixture({ target: 'check-x', wired: true, exempt: { 'check-x': 'stale' } }),
|
|
345
446
|
},
|
|
447
|
+
// The `_lib-export-reach` axis. The accept case comes first so a
|
|
448
|
+
// builder that reds on everything is caught before the three planted
|
|
449
|
+
// failures can be read as the axis working.
|
|
450
|
+
{
|
|
451
|
+
name: 'a whole surface and a called instrument → accept',
|
|
452
|
+
expect: 'accept',
|
|
453
|
+
run: () =>
|
|
454
|
+
inventoryFixture({ surface: wholeSurface, instrument: plainInstrument, calls: true }),
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: 'a surface with no `cap` → reject',
|
|
458
|
+
expect: 'reject',
|
|
459
|
+
run: () =>
|
|
460
|
+
inventoryFixture({ surface: cappedSurface, instrument: plainInstrument, calls: true }),
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: 'an instrument with no consumer → reject',
|
|
464
|
+
expect: 'reject',
|
|
465
|
+
run: () =>
|
|
466
|
+
inventoryFixture({ surface: wholeSurface, instrument: plainInstrument, calls: false }),
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
// The reason `expires:` is a required field rather than a nicety:
|
|
470
|
+
// a declared-inactive instrument with no deadline is the same dead
|
|
471
|
+
// code wearing a label.
|
|
472
|
+
name: 'an exception whose `expires:` has passed → reject',
|
|
473
|
+
expect: 'reject',
|
|
474
|
+
run: () =>
|
|
475
|
+
inventoryFixture({
|
|
476
|
+
surface: wholeSurface,
|
|
477
|
+
instrument: `${plainInstrument}\n status: experimental\n expires: '2020-01-01'`,
|
|
478
|
+
calls: false,
|
|
479
|
+
}),
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: 'an `experimental` exception with no `expires:` → reject',
|
|
483
|
+
expect: 'reject',
|
|
484
|
+
run: () =>
|
|
485
|
+
inventoryFixture({
|
|
486
|
+
surface: wholeSurface,
|
|
487
|
+
instrument: `${plainInstrument}\n status: experimental`,
|
|
488
|
+
calls: false,
|
|
489
|
+
}),
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: 'an unexpired `experimental` exception on a dead instrument → accept',
|
|
493
|
+
expect: 'accept',
|
|
494
|
+
run: () =>
|
|
495
|
+
inventoryFixture({
|
|
496
|
+
surface: wholeSurface,
|
|
497
|
+
instrument: `${plainInstrument}\n status: experimental\n expires: '2099-01-01'`,
|
|
498
|
+
calls: false,
|
|
499
|
+
}),
|
|
500
|
+
},
|
|
346
501
|
];
|
|
347
502
|
try {
|
|
348
|
-
return runSelfTest({ gate: 'check_gate_reachability', cases, minCases:
|
|
503
|
+
return runSelfTest({ gate: 'check_gate_reachability', cases, minCases: 10, minRejectCases: 6 });
|
|
349
504
|
} finally {
|
|
350
505
|
for (const d of roots) fs.rmSync(d, { recursive: true, force: true });
|
|
351
506
|
}
|
|
@@ -355,18 +510,20 @@ export function main(argv: string[] = process.argv.slice(2), root = REPO_ROOT):
|
|
|
355
510
|
if (argv.includes('--self-test')) return selfTest();
|
|
356
511
|
if (argv.includes('--gate')) return runGate(root);
|
|
357
512
|
const r = analyse(root);
|
|
513
|
+
const inv = inventoryVerdict(root);
|
|
358
514
|
if (argv.includes('--json')) {
|
|
359
|
-
process.stdout.write(`${JSON.stringify(r, null, 2)}\n`);
|
|
515
|
+
process.stdout.write(`${JSON.stringify({ ...r, inventory: inv }, null, 2)}\n`);
|
|
360
516
|
return 0;
|
|
361
517
|
}
|
|
362
518
|
// Emitted on the DEFAULT invocation too, not only under `--gate`: the
|
|
363
519
|
// coverage gate reads a bare run, and a gate that reports what it inspected
|
|
364
520
|
// only in one mode is a gate that reports nothing in the other.
|
|
521
|
+
const declared = inv.live.length + inv.exempt.length + inv.reach.length;
|
|
365
522
|
reportScanned({
|
|
366
523
|
gate: 'check_gate_reachability',
|
|
367
|
-
scanned: readExemptions(root).size + r.unreachable.length,
|
|
368
|
-
units: 'exemption row(s) + unreachable target(s)',
|
|
369
|
-
roots: [EXEMPTIONS_REL, ROOT_TASKFILE, TASKFILES_DIR],
|
|
524
|
+
scanned: readExemptions(root).size + r.unreachable.length + declared,
|
|
525
|
+
units: 'exemption row(s) + unreachable target(s) + declared loop instrument(s)',
|
|
526
|
+
roots: [EXEMPTIONS_REL, INVENTORY_REL, ROOT_TASKFILE, TASKFILES_DIR],
|
|
370
527
|
});
|
|
371
528
|
const total = r.reachable.length + r.scriptInWorkflow.length + r.unreachable.length;
|
|
372
529
|
process.stdout.write(
|
|
@@ -375,6 +532,12 @@ export function main(argv: string[] = process.argv.slice(2), root = REPO_ROOT):
|
|
|
375
532
|
`UNREACHABLE ${String(r.unreachable.length)}\n`,
|
|
376
533
|
);
|
|
377
534
|
for (const n of r.unreachable) process.stdout.write(` · ${n}\n`);
|
|
535
|
+
process.stdout.write(
|
|
536
|
+
`declared loop instruments: ${String(declared)} · live ${String(inv.live.length)} · ` +
|
|
537
|
+
`experimental ${String(inv.exempt.length)} · DEAD ${String(inv.reach.length)} · ` +
|
|
538
|
+
`prose-bound surfaces ${String(inv.proseBound.length)}\n`,
|
|
539
|
+
);
|
|
540
|
+
for (const f of inv.reach) process.stdout.write(` · ${f.id} — ${f.detail}\n`);
|
|
378
541
|
return 0;
|
|
379
542
|
}
|
|
380
543
|
|