@event4u/agent-config 16.0.0 → 16.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +297 -137
- package/CONTRIBUTING.md +9 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +26 -0
- package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
- package/dist/agent-src/commands/challenge-me/closure.md +178 -0
- package/dist/agent-src/commands/challenge-me.md +9 -2
- package/dist/agent-src/commands/council/default.md +25 -5
- package/dist/agent-src/commands/feature/plan.md +28 -1
- package/dist/agent-src/commands/feature/roadmap.md +28 -1
- package/dist/agent-src/commands/implement-ticket.md +27 -0
- package/dist/agent-src/commands/jira-ticket.md +27 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/pr/merge.md +60 -9
- package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
- package/dist/agent-src/commands/roadmap/create.md +74 -27
- package/dist/agent-src/commands/roadmap/materialize.md +27 -0
- package/dist/agent-src/commands/roadmap/process-full.md +123 -18
- package/dist/agent-src/commands/team.md +22 -0
- package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
- package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
- package/dist/agent-src/rules/autonomous-execution.md +15 -8
- package/dist/agent-src/rules/delegation-policy.md +23 -45
- package/dist/agent-src/rules/design-fidelity.md +32 -37
- package/dist/agent-src/rules/evaluator-independence.md +43 -49
- package/dist/agent-src/rules/test-first.md +41 -0
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
- package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
- package/dist/agent-src/skills/database/data/manifest.json +4 -4
- package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
- package/dist/agent-src/skills/design-review/SKILL.md +50 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
- package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +188 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/dist/cli-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
- package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
- package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
- package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
- package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
- package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
- package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
- package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
- package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +90 -56
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +9 -7
- package/dist/cli-delegate/cmd_fleet.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_get.js +28 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +10 -8
- package/dist/cli-delegate/cmd_uninstall.js +16 -13
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +12 -10
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +85 -38
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +1832 -1007
- package/dist/install/claudeRuleRewrite.js +14 -3
- package/dist/install/conflict.js +60 -10
- package/dist/install/conflictTracker.js +218 -0
- package/dist/install/fsPrimitives.js +54 -0
- package/dist/install/install.mjs +2007 -1481
- package/dist/install/manifestFiles.js +259 -0
- package/dist/install/preflight.js +14 -2
- package/dist/install/preserve.js +157 -0
- package/dist/install/recordedOwnership.js +132 -0
- package/dist/install/txlog.js +11 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +398 -39
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +3 -2
- package/dist/scripts/_lib/fs_atomic.js +167 -0
- package/dist/scripts/_lib/installed_tools.js +460 -0
- package/dist/scripts/_lib/lean_projection_mode.js +3 -2
- package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +17 -5
- package/dist/shared/missionExecution.js +97 -0
- package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +26 -7
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/docs/guidelines/design-fidelity-routing.md +29 -0
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/src/config/agent-settings.template.yml +139 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +18 -5
- package/src/config/continuity-surface.json +21 -0
- package/src/config/gate-coverage.yml +146 -13
- package/src/config/gate-violation-baselines.json +6 -6
- package/src/config/hook-token-budget.json +2 -0
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/install/claudeRuleRewrite.ts +16 -2
- package/src/install/conflict.ts +65 -9
- package/src/install/conflictTracker.ts +299 -0
- package/src/install/fsPrimitives.ts +54 -0
- package/src/install/manifestFiles.ts +312 -0
- package/src/install/preflight.ts +16 -2
- package/src/install/preserve.ts +205 -0
- package/src/install/recordedOwnership.ts +149 -0
- package/src/install/txlog.ts +11 -2
- package/src/install/types.ts +19 -4
- package/src/scripts/_cli/cmd_conformance.ts +88 -15
- package/src/scripts/_cli/cmd_doctor.ts +4 -3
- package/src/scripts/_cli/cmd_route_explain.ts +9 -2
- package/src/scripts/_cli/cmd_settings_get.ts +44 -0
- package/src/scripts/_cli/doctor_execution.ts +90 -0
- package/src/scripts/_lib/activation_payload.ts +247 -0
- package/src/scripts/_lib/agent_settings.ts +4 -2
- package/src/scripts/_lib/authority_path.ts +102 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- package/src/scripts/_lib/continuation_ladder.ts +149 -2
- package/src/scripts/_lib/council_transport.ts +129 -0
- package/src/scripts/_lib/delivery_ready.ts +112 -0
- package/src/scripts/_lib/forge_protection.ts +267 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/mission_record.ts +190 -0
- package/src/scripts/_lib/obligation_frequency.ts +217 -0
- package/src/scripts/_lib/obligations.ts +402 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/release_highlights.ts +199 -10
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -0
- package/src/scripts/ai_council/config.ts +8 -11
- package/src/scripts/ai_council/fallback_config.ts +5 -3
- package/src/scripts/ai_council/ownership_classes.ts +118 -0
- package/src/scripts/ask_block_census.ts +285 -15
- package/src/scripts/check_enforcement_matrix.ts +624 -0
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +954 -0
- package/src/scripts/check_preamble_payload_budget.ts +75 -2
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +567 -0
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/closure_scan.ts +333 -0
- package/src/scripts/compile_hook_manifest.ts +43 -3
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +88 -9
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/design_pass_hook.ts +128 -3
- package/src/scripts/hooks/evidence_independence.ts +96 -0
- package/src/scripts/hooks/memory_recall_hook.ts +540 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
- package/src/scripts/hooks/rule_inject_hook.ts +49 -1
- package/src/scripts/hooks/run_continuation_hook.ts +17 -20
- package/src/scripts/hooks_doctor.ts +85 -0
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +106 -206
- package/src/scripts/janitor.ts +7 -0
- package/src/scripts/lint_decision_classes.ts +464 -0
- package/src/scripts/lint_hook_manifest.ts +11 -0
- package/src/scripts/lint_major_migration_sections.ts +450 -0
- package/src/scripts/lint_plan_risk_register.ts +47 -3
- package/src/scripts/lint_roadmap_blockers.ts +61 -0
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/release.ts +14 -98
- package/src/scripts/release_drill.ts +267 -1
- package/src/scripts/release_publication.ts +51 -0
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +4 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/trigger_eval_grandfather.json +0 -1
- package/src/scripts/ui_conformance_probe.ts +622 -0
- package/src/server/schemas/settings.ts +31 -7
- package/src/shared/missionExecution.ts +126 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What rides along during a mission — `road-to-adversarial-verification-and-long-runs`
|
|
3
|
+
* 6.1.
|
|
4
|
+
*
|
|
5
|
+
* `active-remediation`'s ladder is fix-now / note+ask / follow-up PR, and its
|
|
6
|
+
* middle rung is unavailable inside an autonomous mission: there is nobody to
|
|
7
|
+
* ask. Without a replacement the rung collapses in one of two bad directions —
|
|
8
|
+
* every spotted issue becomes a fix (scope creep, which `minimal-safe-diff`
|
|
9
|
+
* exists to stop) or every one becomes silence (which `active-remediation`
|
|
10
|
+
* exists to stop). So under a mission the middle rung becomes **emit a follow-up
|
|
11
|
+
* artefact**, and this module decides which rung a candidate lands on.
|
|
12
|
+
*
|
|
13
|
+
* **Six criteria, ALL of which must hold.** They are `AND`-ed rather than scored
|
|
14
|
+
* because a scored version lets a large change buy its way in with four cheap
|
|
15
|
+
* yeses, which is the shape that turns a boy-scout rule into a refactor licence.
|
|
16
|
+
*
|
|
17
|
+
* Pure: the caller characterises the change, this decides. The characterisation
|
|
18
|
+
* is a judgement and stays the agent's; the DECISION is mechanical so it is the
|
|
19
|
+
* same for the same inputs.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** What the agent observed about a candidate improvement. */
|
|
23
|
+
export interface Candidate {
|
|
24
|
+
/** What it is. Used only in the rendered line. */
|
|
25
|
+
readonly what: string;
|
|
26
|
+
/** In the same request path / module the mission is already touching. */
|
|
27
|
+
readonly local: boolean;
|
|
28
|
+
/** Small: roughly a handful of lines in one production file. */
|
|
29
|
+
readonly small: boolean;
|
|
30
|
+
/** Low blast radius — nothing outside this module observes the change. */
|
|
31
|
+
readonly lowBlastRadius: boolean;
|
|
32
|
+
/** Clearly correct: a reviewer would not need to be persuaded. */
|
|
33
|
+
readonly clearlyCorrect: boolean;
|
|
34
|
+
/** Testable, and the test ships in the same commit. */
|
|
35
|
+
readonly testable: boolean;
|
|
36
|
+
/** Carries NO new product decision — nothing about what the thing should do. */
|
|
37
|
+
readonly noProductDecision: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The kinds of improvement a mission may take at all. */
|
|
41
|
+
export const RIDE_ALONG_KINDS: readonly string[] = [
|
|
42
|
+
'characterization-test',
|
|
43
|
+
'regression-test',
|
|
44
|
+
'small-adjacent-bug',
|
|
45
|
+
'naming',
|
|
46
|
+
'types',
|
|
47
|
+
'robustness',
|
|
48
|
+
'local-dead-code',
|
|
49
|
+
'simplify-touched-code',
|
|
50
|
+
'testability',
|
|
51
|
+
] as const;
|
|
52
|
+
|
|
53
|
+
export type Disposition = 'ride-along' | 'follow-up-artefact';
|
|
54
|
+
|
|
55
|
+
export interface Verdict {
|
|
56
|
+
readonly disposition: Disposition;
|
|
57
|
+
/** Every criterion that failed, in declaration order. Empty on ride-along. */
|
|
58
|
+
readonly failed: readonly string[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const CRITERIA: readonly (readonly [keyof Candidate, string])[] = [
|
|
62
|
+
['local', 'not local to the path this mission is already touching'],
|
|
63
|
+
['small', 'not small — more than a handful of lines in one file'],
|
|
64
|
+
['lowBlastRadius', 'blast radius reaches beyond this module'],
|
|
65
|
+
['clearlyCorrect', 'not clearly correct — a reviewer would need persuading'],
|
|
66
|
+
['testable', 'not testable in the same commit'],
|
|
67
|
+
['noProductDecision', 'carries a new product decision'],
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Which rung this candidate lands on.
|
|
72
|
+
*
|
|
73
|
+
* Returns EVERY failed criterion rather than the first, because the follow-up
|
|
74
|
+
* artefact has to say why the change was deferred, and "it failed one of six"
|
|
75
|
+
* is not a reason a later reader can act on.
|
|
76
|
+
*/
|
|
77
|
+
export function disposition(c: Candidate): Verdict {
|
|
78
|
+
const failed = CRITERIA.filter(([k]) => c[k] !== true).map(([, why]) => why);
|
|
79
|
+
return {
|
|
80
|
+
disposition: failed.length === 0 ? 'ride-along' : 'follow-up-artefact',
|
|
81
|
+
failed,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The follow-up artefact's body — what was seen, and why it did not ride along.
|
|
87
|
+
*
|
|
88
|
+
* A deferral with no reason is the note-and-forget `active-remediation` already
|
|
89
|
+
* forbids, one layer out: the artefact exists so the issue is still owed, and an
|
|
90
|
+
* artefact that does not say why it was deferred cannot be triaged later.
|
|
91
|
+
*/
|
|
92
|
+
export function followUpBody(c: Candidate, v: Verdict): string {
|
|
93
|
+
if (v.disposition === 'ride-along') return '';
|
|
94
|
+
return [
|
|
95
|
+
`Deferred adjacent improvement: ${c.what}`,
|
|
96
|
+
'',
|
|
97
|
+
'Did not ride along because:',
|
|
98
|
+
...v.failed.map((f) => ` - ${f}`),
|
|
99
|
+
'',
|
|
100
|
+
'Spotted during a mission; the code was left alone deliberately.',
|
|
101
|
+
].join('\n');
|
|
102
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings keys this package has RENAMED, old path -> new path.
|
|
3
|
+
*
|
|
4
|
+
* Distinct from `REMOVED_KEYS` in the one way that matters to a user: a
|
|
5
|
+
* removed key's value is DISCARDED, and a renamed key's value is CARRIED.
|
|
6
|
+
* Putting a rename in the removed map would silently return an installed
|
|
7
|
+
* `false` to the shipped default, which is the opposite of accepting the old
|
|
8
|
+
* key — so the two maps stay separate even though both warn once.
|
|
9
|
+
*
|
|
10
|
+
* An entry is dropped one minor after it lands. The new key always wins when
|
|
11
|
+
* one layer carries both: a reader who has already migrated is not overridden
|
|
12
|
+
* by a stale line they forgot to delete.
|
|
13
|
+
*
|
|
14
|
+
* Its own module rather than more lines in `agent_settings.ts`, which sits
|
|
15
|
+
* three lines under the 1500-line source ceiling — a sixty-line addition there
|
|
16
|
+
* is sixty units of ratchet, and here it is none.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type SettingsLayer = Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
export const RENAMED_KEYS: ReadonlyMap<string, string> = new Map([
|
|
22
|
+
['planning.challenge_on_create', 'planning.closure_pass'],
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
/** Keys already warned about in THIS process — the "once per run" dedupe. */
|
|
26
|
+
const _warned = new Set<string>();
|
|
27
|
+
|
|
28
|
+
/** Read a dotted path out of a settings tree. `undefined` when absent. */
|
|
29
|
+
function _read(tree: SettingsLayer, dotted: string): unknown {
|
|
30
|
+
let cur: unknown = tree;
|
|
31
|
+
for (const part of dotted.split('.')) {
|
|
32
|
+
if (cur === null || typeof cur !== 'object' || Array.isArray(cur)) return undefined;
|
|
33
|
+
cur = (cur as SettingsLayer)[part];
|
|
34
|
+
}
|
|
35
|
+
return cur;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Write a dotted path into a settings tree, creating the intermediate maps. */
|
|
39
|
+
function _write(tree: SettingsLayer, dotted: string, value: unknown): void {
|
|
40
|
+
const parts = dotted.split('.');
|
|
41
|
+
let cur: SettingsLayer = tree;
|
|
42
|
+
for (const part of parts.slice(0, -1)) {
|
|
43
|
+
const next = cur[part];
|
|
44
|
+
if (next === null || typeof next !== 'object' || Array.isArray(next)) cur[part] = {};
|
|
45
|
+
cur = cur[part] as SettingsLayer;
|
|
46
|
+
}
|
|
47
|
+
cur[parts[parts.length - 1] as string] = value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Carry every renamed key's value onto its new path within ONE layer, and
|
|
52
|
+
* return that layer.
|
|
53
|
+
*
|
|
54
|
+
* Per LAYER, before the merge, and that ordering is the whole contract: the
|
|
55
|
+
* shipped template already carries the new key, so a rename applied to the
|
|
56
|
+
* MERGED tree would always find the target "already set" and drop the user's
|
|
57
|
+
* value on the floor. Applied per layer, the old key becomes the new key
|
|
58
|
+
* inside the layer that wrote it, and ordinary precedence does the rest.
|
|
59
|
+
*
|
|
60
|
+
* Mutates and returns `layer` so it can wrap an existing merge call without
|
|
61
|
+
* adding a statement. Never throws: a layer that cannot be walked is a layer
|
|
62
|
+
* with nothing to migrate.
|
|
63
|
+
*/
|
|
64
|
+
export function applyRenamedKeys<T extends SettingsLayer>(layer: T): T {
|
|
65
|
+
for (const [oldKey, newKey] of RENAMED_KEYS) {
|
|
66
|
+
const oldValue = _read(layer, oldKey);
|
|
67
|
+
if (oldValue === undefined) continue;
|
|
68
|
+
if (_read(layer, newKey) === undefined) _write(layer, newKey, oldValue);
|
|
69
|
+
if (_warned.has(oldKey)) continue;
|
|
70
|
+
_warned.add(oldKey);
|
|
71
|
+
process.stderr.write(
|
|
72
|
+
`⚠️ settings: \`${oldKey}\` was renamed to \`${newKey}\`. The old key still ` +
|
|
73
|
+
`works for one minor; rename it in your .agent-settings.yml.\n`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return layer;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Test seam — the once-per-process warning dedupe is process state. */
|
|
80
|
+
export function _resetRenameWarnings(): void {
|
|
81
|
+
_warned.clear();
|
|
82
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared classification for the two test gates —
|
|
3
|
+
* `road-to-adversarial-verification-and-long-runs` Phase 2.4.
|
|
4
|
+
*
|
|
5
|
+
* `check_test_delta` and `check_test_weakening` ask different questions about
|
|
6
|
+
* the same diff, and both need the same answer to "is this path code?" and "is
|
|
7
|
+
* this path a test?". Two copies of that answer is how one gate starts counting
|
|
8
|
+
* a file the other does not, so the predicates live here once.
|
|
9
|
+
*
|
|
10
|
+
* Pure, and `node:`-free, so either gate and their tests may import it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** A changed path that is production code this repository ships or runs. */
|
|
14
|
+
export function isCodePath(p: string): boolean {
|
|
15
|
+
const f = p.replace(/\\/g, '/');
|
|
16
|
+
if (isTestPath(f)) return false;
|
|
17
|
+
if (!/\.(ts|tsx|js|mjs|cjs)$/.test(f)) return false;
|
|
18
|
+
if (f.endsWith('.d.ts')) return false;
|
|
19
|
+
// Generated trees are projections of `src/`, never an independent change.
|
|
20
|
+
if (/^(dist|\.augment|\.claude|\.cursor|node_modules)\//.test(f)) return false;
|
|
21
|
+
return /^(src|scripts)\//.test(f);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A changed path that carries tests. */
|
|
25
|
+
export function isTestPath(p: string): boolean {
|
|
26
|
+
const f = p.replace(/\\/g, '/');
|
|
27
|
+
if (/^tests\//.test(f)) return true;
|
|
28
|
+
return /\.(test|spec)\.(ts|tsx|js|mjs|cjs)$/.test(f);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* An independent-verdict artefact — the thing that makes a weakening legitimate.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately a PATH test and not a content test. Whether the artefact actually
|
|
35
|
+
* contains an independent verdict is exactly the judgement `evaluator-independence`
|
|
36
|
+
* says no gate can make, so this gate checks that one was COMMITTED and leaves
|
|
37
|
+
* its quality to the review. Claiming more would be the coverage inflation that
|
|
38
|
+
* rule was written over.
|
|
39
|
+
*/
|
|
40
|
+
export function isVerdictArtefact(p: string): boolean {
|
|
41
|
+
const f = p.replace(/\\/g, '/');
|
|
42
|
+
return /^agents\/evidence\/reviews\//.test(f) && f.endsWith('.md');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One file's hunks, as a unified diff splits them. */
|
|
46
|
+
export interface FileDiff {
|
|
47
|
+
readonly path: string;
|
|
48
|
+
readonly added: readonly string[];
|
|
49
|
+
readonly removed: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Split a unified diff into per-file added/removed line lists.
|
|
54
|
+
*
|
|
55
|
+
* Tolerant by construction: an unparseable header is skipped rather than thrown
|
|
56
|
+
* on, because a gate that crashes on an unusual diff blocks the fix for the
|
|
57
|
+
* diff that produced it. `+++ b/<path>` is the authority for the path — a rename
|
|
58
|
+
* shows the destination, which is the file whose assertions now count.
|
|
59
|
+
*/
|
|
60
|
+
export function parseUnifiedDiff(diff: string): FileDiff[] {
|
|
61
|
+
const out: FileDiff[] = [];
|
|
62
|
+
let path: string | null = null;
|
|
63
|
+
let oldPath: string | null = null;
|
|
64
|
+
let added: string[] = [];
|
|
65
|
+
let removed: string[] = [];
|
|
66
|
+
const flush = (): void => {
|
|
67
|
+
if (path !== null) out.push({ path, added, removed });
|
|
68
|
+
added = [];
|
|
69
|
+
removed = [];
|
|
70
|
+
};
|
|
71
|
+
for (const line of diff.split('\n')) {
|
|
72
|
+
if (line.startsWith('diff --git ')) {
|
|
73
|
+
flush();
|
|
74
|
+
path = null;
|
|
75
|
+
oldPath = null;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (line.startsWith('--- ')) {
|
|
79
|
+
const p = line.slice(4).trim();
|
|
80
|
+
oldPath = p === '/dev/null' ? null : p.replace(/^a\//, '');
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (line.startsWith('+++ ')) {
|
|
84
|
+
const p = line.slice(4).trim();
|
|
85
|
+
// A DELETION (`+++ /dev/null`) keeps the OLD path rather than
|
|
86
|
+
// dropping the file. Reported by an independent review of the change
|
|
87
|
+
// that introduced this parser: deleting a test file is the maximal
|
|
88
|
+
// form of the weakening this gate exists to see, and `git rm
|
|
89
|
+
// tests/x.test.ts` was the cheapest way past it — the deletion even
|
|
90
|
+
// counted as a `tests/` path for the sibling delta gate. A file whose
|
|
91
|
+
// every assertion is removed at once is not zero signal.
|
|
92
|
+
path = p === '/dev/null' ? oldPath : p.replace(/^b\//, '');
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (line.startsWith('@@')) continue;
|
|
96
|
+
if (path === null) continue;
|
|
97
|
+
if (line.startsWith('+')) added.push(line.slice(1));
|
|
98
|
+
else if (line.startsWith('-')) removed.push(line.slice(1));
|
|
99
|
+
}
|
|
100
|
+
flush();
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Lines that assert something.
|
|
106
|
+
*
|
|
107
|
+
* Framework-spread on purpose — this suite runs vitest, and the rule it enforces
|
|
108
|
+
* ships to consumers running pytest, PHPUnit and Go. A gate that only recognises
|
|
109
|
+
* one framework reports every other project as assertion-free, which is a false
|
|
110
|
+
* green and worse than no gate.
|
|
111
|
+
*/
|
|
112
|
+
const ASSERTION = /\b(expect|assert|assertThat|self\.assert|should\.|\.toBe|\.toEqual|\.toMatch|assertEquals|assertTrue|require\.(New|Equal|NoError))/;
|
|
113
|
+
|
|
114
|
+
/** Lines that disable a test rather than assert with it. */
|
|
115
|
+
const SUPPRESSION =
|
|
116
|
+
/(\b(it|test|describe|context)\s*\.\s*(skip|todo|failing)\b|\bxit\b|\bxdescribe\b|@pytest\.mark\.(skip|xfail)|\bt\.Skip\(|->markTestSkipped\(|@group\s+skip)/;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A line whose match is DATA, not code — a diff line held inside a string.
|
|
120
|
+
*
|
|
121
|
+
* Found by this gate reporting its own test file: a suite that tests a
|
|
122
|
+
* suppression detector necessarily contains suppression-shaped strings as
|
|
123
|
+
* fixtures (`'+@pytest.mark.skip\n'`), and counting those made the gate report
|
|
124
|
+
* the one file whose job is to prove it works. Generalised rather than
|
|
125
|
+
* special-cased to that path — every gate that reads diffs will have a test
|
|
126
|
+
* suite shaped exactly this way, and "the gate exempts its own tests" is the
|
|
127
|
+
* carve-out that makes a detector unfalsifiable.
|
|
128
|
+
*
|
|
129
|
+
* The discriminator is a quote immediately followed by a diff marker: a string
|
|
130
|
+
* literal whose content begins `+` or `-`. Real test code does not open a string
|
|
131
|
+
* with a diff marker; a diff fixture does nothing else.
|
|
132
|
+
*/
|
|
133
|
+
const DIFF_LINE_AS_DATA = /['"`][+-]/;
|
|
134
|
+
|
|
135
|
+
/** Counted evidence about one test file's diff. */
|
|
136
|
+
export interface WeakeningSignal {
|
|
137
|
+
readonly path: string;
|
|
138
|
+
/** Assertions removed minus assertions added. Positive means a net loss. */
|
|
139
|
+
readonly assertionsLost: number;
|
|
140
|
+
/** Suppressions added minus suppressions removed. Positive means new skips. */
|
|
141
|
+
readonly suppressionsGained: number;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Measure a test file's diff as NET counts, never as raw removals.
|
|
146
|
+
*
|
|
147
|
+
* This is the discriminator the gate lives or dies on. Editing an assertion
|
|
148
|
+
* removes one line and adds another, so a gate counting raw removals reds every
|
|
149
|
+
* legitimate test edit — and a gate that reds every PR gets its exemption widened
|
|
150
|
+
* until it finds nothing, which is Risk 4 of the roadmap that commissioned it.
|
|
151
|
+
* A NET loss is a different claim: the file asserts less than it did.
|
|
152
|
+
*/
|
|
153
|
+
export function weakeningSignal(file: FileDiff): WeakeningSignal {
|
|
154
|
+
const count = (lines: readonly string[], re: RegExp): number =>
|
|
155
|
+
lines.filter((l) => re.test(l) && !DIFF_LINE_AS_DATA.test(l)).length;
|
|
156
|
+
return {
|
|
157
|
+
path: file.path,
|
|
158
|
+
assertionsLost: count(file.removed, ASSERTION) - count(file.added, ASSERTION),
|
|
159
|
+
suppressionsGained: count(file.added, SUPPRESSION) - count(file.removed, SUPPRESSION),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** True when a signal is worth reporting at all. */
|
|
164
|
+
export function isWeakening(s: WeakeningSignal): boolean {
|
|
165
|
+
return s.assertionsLost > 0 || s.suppressionsGained > 0;
|
|
166
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-test independence provenance — `road-to-adversarial-verification-and-long-runs` AC-2.
|
|
3
|
+
*
|
|
4
|
+
* `evaluator-independence` § Tests are evaluators states the five levels and
|
|
5
|
+
* says critical behaviour targets L3 or L4 wherever two providers are
|
|
6
|
+
* configured. It states no place to WRITE that down, so before this module the
|
|
7
|
+
* level a given test actually reached was unrecorded and therefore uncheckable —
|
|
8
|
+
* which is the same shape as the recorded failure that rule exists over: a
|
|
9
|
+
* verdict nobody could trace back to the prompt that produced it.
|
|
10
|
+
*
|
|
11
|
+
* The record is a comment line directly above a `describe(`:
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* // provenance: level=L4 | critical=yes | evidence=ac2-authorship-run-a
|
|
15
|
+
* describe('T9 — a typed op reaches an exact-object ask', () => {
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* **Three decisions worth reading, because each has a plausible opposite.**
|
|
19
|
+
*
|
|
20
|
+
* An unmarked group is `ungoverned`, never defaulted to L0. Defaulting would
|
|
21
|
+
* make "provenance is recorded per test" true by construction, and a claim that
|
|
22
|
+
* cannot be false is not a record.
|
|
23
|
+
*
|
|
24
|
+
* The provider count is a PARAMETER, not a probe. `agent-config council:status`
|
|
25
|
+
* resolves from a user-global file that a CI runner does not have, so probing
|
|
26
|
+
* it would make the floor depend on the machine and read "no providers, no
|
|
27
|
+
* obligation" exactly where the obligation matters. The caller supplies the
|
|
28
|
+
* count it is asserting about, and a repository with two configured providers
|
|
29
|
+
* pins 2.
|
|
30
|
+
*
|
|
31
|
+
* `evidence` is required at L3 and L4 and ignored below. An unattributed claim
|
|
32
|
+
* of independence cannot be checked for the independence it claims; at L0-L2
|
|
33
|
+
* there is no cross-provider claim to attribute, so demanding an artefact there
|
|
34
|
+
* would be ceremony.
|
|
35
|
+
*
|
|
36
|
+
* Pure — a string in, findings out. No filesystem, so both polarities are
|
|
37
|
+
* testable without a fixture tree.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** The five levels of `evaluator-independence` § Tests are evaluators. */
|
|
41
|
+
export const INDEPENDENCE_LEVELS = ['L0', 'L1', 'L2', 'L3', 'L4'] as const;
|
|
42
|
+
export type IndependenceLevel = (typeof INDEPENDENCE_LEVELS)[number];
|
|
43
|
+
|
|
44
|
+
/** What critical behaviour targets where two providers are configured. */
|
|
45
|
+
export const CRITICAL_FLOOR: IndependenceLevel = 'L3';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The honest ceiling below two providers.
|
|
49
|
+
*
|
|
50
|
+
* With one provider, L3 ("another provider") is unreachable by definition, so
|
|
51
|
+
* the floor relaxes rather than demanding a level the install cannot produce. A
|
|
52
|
+
* floor nobody can clear gets deleted rather than met.
|
|
53
|
+
*/
|
|
54
|
+
export const SINGLE_PROVIDER_FLOOR: IndependenceLevel = 'L2';
|
|
55
|
+
|
|
56
|
+
/** One group's recorded independence. */
|
|
57
|
+
export interface ProvenanceRecord {
|
|
58
|
+
/** The `describe` title this record governs. */
|
|
59
|
+
readonly group: string;
|
|
60
|
+
readonly level: IndependenceLevel;
|
|
61
|
+
/** Security, authority, data loss or merge control. */
|
|
62
|
+
readonly critical: boolean;
|
|
63
|
+
/** The artefact the level came from. Empty is legal below L3. */
|
|
64
|
+
readonly evidence: string;
|
|
65
|
+
/** 1-based line of the marker, so a finding can point at it. */
|
|
66
|
+
readonly line: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ProvenanceScan {
|
|
70
|
+
readonly records: readonly ProvenanceRecord[];
|
|
71
|
+
/** Titles of `describe`s carrying no valid marker. */
|
|
72
|
+
readonly ungoverned: readonly string[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Rank within the ordered vocabulary. L0 is 0. */
|
|
76
|
+
export function levelRank(level: IndependenceLevel): number {
|
|
77
|
+
return INDEPENDENCE_LEVELS.indexOf(level);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const MARKER = /^\s*\/\/\s*provenance:\s*(.*)$/;
|
|
81
|
+
const DESCRIBE = /^\s*describe\(\s*(['"`])(.*?)\1/;
|
|
82
|
+
|
|
83
|
+
function isLevel(v: string): v is IndependenceLevel {
|
|
84
|
+
return (INDEPENDENCE_LEVELS as readonly string[]).includes(v);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Parse `key=value | key=value` into a map. Unknown keys are ignored. */
|
|
88
|
+
function fields(rest: string): Map<string, string> {
|
|
89
|
+
const out = new Map<string, string>();
|
|
90
|
+
for (const part of rest.split('|')) {
|
|
91
|
+
const eq = part.indexOf('=');
|
|
92
|
+
if (eq < 0) continue;
|
|
93
|
+
out.set(part.slice(0, eq).trim(), part.slice(eq + 1).trim());
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Scan a test file's source for provenance records.
|
|
100
|
+
*
|
|
101
|
+
* A marker governs the `describe` it sits directly above — blank lines between
|
|
102
|
+
* them are tolerated, any other statement is not. Without that adjacency rule a
|
|
103
|
+
* marker anywhere in the file would appear to cover the next group, which is how
|
|
104
|
+
* one record silently becomes a claim about several.
|
|
105
|
+
*/
|
|
106
|
+
export function parseProvenance(source: string): ProvenanceScan {
|
|
107
|
+
const lines = source.split('\n');
|
|
108
|
+
const records: ProvenanceRecord[] = [];
|
|
109
|
+
const ungoverned: string[] = [];
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
112
|
+
const d = DESCRIBE.exec(lines[i] ?? '');
|
|
113
|
+
if (d === null) continue;
|
|
114
|
+
const group = d[2] ?? '';
|
|
115
|
+
|
|
116
|
+
// Walk back over blank lines to the candidate marker.
|
|
117
|
+
let j = i - 1;
|
|
118
|
+
while (j >= 0 && (lines[j] ?? '').trim() === '') j -= 1;
|
|
119
|
+
const m = j >= 0 ? MARKER.exec(lines[j] ?? '') : null;
|
|
120
|
+
if (m === null) {
|
|
121
|
+
ungoverned.push(group);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const f = fields(m[1] ?? '');
|
|
125
|
+
const level = f.get('level') ?? '';
|
|
126
|
+
if (!isLevel(level)) {
|
|
127
|
+
ungoverned.push(group);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
records.push({
|
|
131
|
+
group,
|
|
132
|
+
level,
|
|
133
|
+
critical: f.get('critical') === 'yes',
|
|
134
|
+
evidence: f.get('evidence') ?? '',
|
|
135
|
+
line: j + 1,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return { records, ungoverned };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Critical groups that do not clear the floor for this provider count.
|
|
143
|
+
*
|
|
144
|
+
* Returns one human-readable finding per gap, naming the group, the level it
|
|
145
|
+
* records and what it owed — a caller reading the list should not have to open
|
|
146
|
+
* the file to know what to change.
|
|
147
|
+
*/
|
|
148
|
+
export function criticalGaps(
|
|
149
|
+
records: readonly ProvenanceRecord[],
|
|
150
|
+
providerCount: number,
|
|
151
|
+
): string[] {
|
|
152
|
+
const floor = providerCount >= 2 ? CRITICAL_FLOOR : SINGLE_PROVIDER_FLOOR;
|
|
153
|
+
const gaps: string[] = [];
|
|
154
|
+
for (const r of records) {
|
|
155
|
+
if (!r.critical) continue;
|
|
156
|
+
if (levelRank(r.level) < levelRank(floor)) {
|
|
157
|
+
gaps.push(
|
|
158
|
+
`${r.group} (line ${String(r.line)}) records ${r.level}; critical behaviour ` +
|
|
159
|
+
`needs ${floor} or above with ${String(providerCount)} provider(s) configured`,
|
|
160
|
+
);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (levelRank(r.level) >= levelRank(CRITICAL_FLOOR) && r.evidence === '') {
|
|
164
|
+
gaps.push(
|
|
165
|
+
`${r.group} (line ${String(r.line)}) claims ${r.level} with no evidence; an ` +
|
|
166
|
+
`unattributed claim of independence cannot be checked for the independence it claims`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return gaps;
|
|
171
|
+
}
|