@event4u/agent-config 9.0.0 → 9.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +22 -204
- package/MIGRATION.md +14 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/explain-run.md +88 -0
- package/dist/agent-src/commands/implement-ticket.md +2 -0
- package/dist/agent-src/commands/pr/create/description-only.md +138 -8
- package/dist/agent-src/commands/pr/create.md +16 -5
- package/dist/agent-src/commands/review/changes.md +22 -0
- package/dist/agent-src/contexts/execution/subagent-modes-detail.md +41 -6
- package/dist/agent-src/contexts/execution/verify-budget.md +15 -0
- package/dist/agent-src/rules/cross-source-consistency.md +92 -0
- package/dist/agent-src/rules/delegation-policy.md +1 -1
- package/dist/agent-src/skills/estimate-ticket/SKILL.md +2 -0
- package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
- package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +146 -0
- package/dist/agent-src/skills/llm-provider-knowledge/evals/triggers.json +20 -0
- package/dist/agent-src/skills/po-discovery/SKILL.md +2 -0
- package/dist/agent-src/skills/refine-ticket/SKILL.md +7 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +14 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +26 -5
- package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +18 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +14 -2
- package/dist/agent-src/skills/subagent-orchestration/prompts/adversarial-verification-council.md +79 -0
- package/dist/agent-src/skills/subagent-orchestration/schemas/adversarial-findings.json +56 -0
- package/dist/agent-src/skills/token-optimizer/SKILL.md +10 -4
- package/dist/agent-src/templates/agent-settings.md +28 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +4 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +105 -28
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +5 -5
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +16 -12
- package/dist/discovery/trust-report.md +8 -8
- package/dist/discovery/workspaces.json +12 -8
- package/dist/install/install.mjs +47 -31
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +4 -1
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/server/schemas/settings.js +10 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +10 -3
- package/docs/MIGRATION.md +20 -0
- package/docs/architecture.md +4 -4
- package/docs/archive/CHANGELOG-pre-9.2.0.md +276 -0
- package/docs/benchmark.md +24 -0
- package/docs/command-flows.md +1 -1
- package/docs/contracts/agents-layout.md +1 -1
- package/docs/contracts/consumer-bridge.md +55 -63
- package/docs/contracts/install-layout.md +1 -1
- package/docs/contracts/migrate-command.md +1 -1
- package/docs/customization.md +22 -1
- package/docs/decisions/ADR-020-global-only-consumer-scope.md +34 -0
- package/docs/decisions/ADR-122-adversarial-verification-council.md +136 -0
- package/docs/decisions/INDEX.md +1 -0
- package/docs/design/adversarial-council-eval.md +106 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/api-cost-levers.md +126 -0
- package/docs/guidelines/agent-infra/cross-source-consistency-mechanics.md +109 -0
- package/docs/installation.md +2 -1
- package/docs/maintainers/dev-mode.md +3 -2
- package/docs/proof.md +7 -7
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +4 -1
- package/src/config/agent-settings.template.yml +81 -0
- package/src/config/gitignore-block.txt +16 -6
- package/src/scripts/_cli/cmd_conformance.ts +2 -2
- package/src/scripts/_cli/cmd_doctor.ts +162 -13
- package/src/scripts/_cli/cmd_refresh.ts +35 -118
- package/src/scripts/_lib/adversarial_council_gate.test.ts +112 -0
- package/src/scripts/_lib/adversarial_council_gate.ts +98 -0
- package/src/scripts/_lib/adversarial_reconcile.test.ts +197 -0
- package/src/scripts/_lib/adversarial_reconcile.ts +187 -0
- package/src/scripts/_lib/agent_settings.ts +4 -1
- package/src/scripts/_lib/verify_budget.ts +4 -1
- package/src/scripts/ai_council/clients.ts +68 -6
- package/src/scripts/ai_council/pricing.ts +62 -0
- package/src/scripts/council_cli.ts +19 -3
- package/src/scripts/install.ts +44 -45
- package/src/scripts/lint_agents_layout.ts +10 -4
- package/src/scripts/lint_breaking_changes_index.ts +303 -0
- package/src/scripts/mcp_server/tools.ts +1 -3
- package/src/scripts/schemas/agent-settings.schema.json +28 -0
- package/src/scripts/self_review_gate.test.ts +11 -0
- package/src/scripts/self_review_gate.ts +7 -1
- package/src/scripts/skill_eval_coverage.ts +4 -1
- package/src/server/schemas/settings.ts +24 -0
|
@@ -518,6 +518,34 @@ design:
|
|
|
518
518
|
# autonomy setting, roadmap, or standing instruction lifts it).
|
|
519
519
|
fidelity_mode: strict
|
|
520
520
|
|
|
521
|
+
# --- Cross-source consistency ---
|
|
522
|
+
#
|
|
523
|
+
# When the agent plans, refines, roadmaps, or implements from multiple
|
|
524
|
+
# sources — ticket text, an attached image / mockup / screenshot, the spec,
|
|
525
|
+
# the codebase — it actively checks them AGAINST each other and STOPS to ask
|
|
526
|
+
# before proceeding on a detected discrepancy, instead of silently guessing
|
|
527
|
+
# (even a correct guess). Consumed by the `cross-source-consistency` rule.
|
|
528
|
+
# Catches: (a) ticket text vs an attached image/mockup, (b) a spec silent on
|
|
529
|
+
# a clearly-needed behavior (weekend/holiday shifting, empty/error states),
|
|
530
|
+
# (c) spec vs the codebase, (d) intra-ticket contradiction (AC vs description).
|
|
531
|
+
#
|
|
532
|
+
# The discipline is deliberately subordinate to `ask-when-uncertain` (it folds
|
|
533
|
+
# a detected discrepancy into the same turn's single question — never a second
|
|
534
|
+
# prompt) and defers to `scope-control` for the permission to expand scope.
|
|
535
|
+
consistency:
|
|
536
|
+
# on | auto | off (default: on)
|
|
537
|
+
#
|
|
538
|
+
# on = default. Detect discrepancies across sources and surface ONE
|
|
539
|
+
# numbered-options question before proceeding. A silent guess on a
|
|
540
|
+
# real contradiction is a rule violation, even when the guess is
|
|
541
|
+
# correct. An inferred-but-unspecified behavior is a scope expansion:
|
|
542
|
+
# ask before implementing it.
|
|
543
|
+
# auto = detect + surface only HIGH-confidence contradictions; low-confidence
|
|
544
|
+
# ones are stated as an assumption and proceed (noise control).
|
|
545
|
+
# off = no cross-source checking (legacy: the agent may proceed on its own
|
|
546
|
+
# reading of whichever source it happened to read).
|
|
547
|
+
cross_source: "on"
|
|
548
|
+
|
|
521
549
|
code_style:
|
|
522
550
|
# minimal | full (default: minimal)
|
|
523
551
|
#
|
|
@@ -604,6 +632,20 @@ subagents:
|
|
|
604
632
|
# Integer, default 3. Set to 1 to serialize. Hard cap enforced by runtime.
|
|
605
633
|
max_parallel: 3
|
|
606
634
|
|
|
635
|
+
# Opt-in adversarial-verification-council (subagent-orchestration Mode 9,
|
|
636
|
+
# ADR-122): off | ask | on
|
|
637
|
+
#
|
|
638
|
+
# off (default) = never runs. ask = offer it on an explicit high-risk change.
|
|
639
|
+
# on = auto-run on high-risk changes (security, tenant, migration, public API).
|
|
640
|
+
#
|
|
641
|
+
# A panel of distinct-model skeptics red-teams a real, already-verified change
|
|
642
|
+
# for defect FINDING coverage (not a go/no-go decision) and returns an advisory
|
|
643
|
+
# findings-by-severity verdict. ADVISORY ONLY — it NEVER auto-gates the change
|
|
644
|
+
# (Hard Floor). The registered claim + high-risk tier use cross-vendor skeptics.
|
|
645
|
+
# Stays default-off until the adversarial-council-finding-coverage claim is
|
|
646
|
+
# backed by the two-stage residual-detection benchmark (prove-or-drop).
|
|
647
|
+
adversarial_council: off
|
|
648
|
+
|
|
607
649
|
# --- Git worktrees ---
|
|
608
650
|
#
|
|
609
651
|
# Controls how the `using-git-worktrees` skill and `subagent-orchestration`
|
|
@@ -761,6 +803,45 @@ commands:
|
|
|
761
803
|
create_pr:
|
|
762
804
|
preview_description: false
|
|
763
805
|
|
|
806
|
+
# Verbosity tier for the generated PR description body.
|
|
807
|
+
# min (default) = title + 2-3 sentence what/why/impact + linked ticket.
|
|
808
|
+
# Token-frugal. Critical info (breaking changes,
|
|
809
|
+
# migrations, security, rollback) is ALWAYS included
|
|
810
|
+
# regardless of tier — the tier governs explanatory
|
|
811
|
+
# depth, never whether a critical callout appears.
|
|
812
|
+
# med = min + grouped logical changes + tests note.
|
|
813
|
+
# max = med + how-to-test + edge cases / trade-offs +
|
|
814
|
+
# reviewer guidance (the richest, ~legacy output).
|
|
815
|
+
detail_level: min
|
|
816
|
+
|
|
817
|
+
# JSON request/response examples for API-endpoint changes.
|
|
818
|
+
# true (default) = include a fenced example ONLY when it can be
|
|
819
|
+
# grounded in a real source (response DTO/resource,
|
|
820
|
+
# OpenAPI/schema, test fixture, or an actual probe).
|
|
821
|
+
# No grounded source → a one-line pointer, never an
|
|
822
|
+
# invented example (a hallucinated contract misleads
|
|
823
|
+
# reviewers, so it is worse than none).
|
|
824
|
+
# false = never add API examples.
|
|
825
|
+
api_examples: true
|
|
826
|
+
|
|
827
|
+
# Screenshots for frontend changes. Default false (off). Capability-gated:
|
|
828
|
+
# requires the host to have browser/preview tooling (Playwright / a
|
|
829
|
+
# dev-server URL). When true + a frontend change + no such tooling, the
|
|
830
|
+
# agent emits a one-line note and leaves the template placeholder — it
|
|
831
|
+
# never fails or blocks PR creation, and never claims an embed that did
|
|
832
|
+
# not happen. Before/after + changed-region highlighting is best-effort.
|
|
833
|
+
# false (default) = never attempt screenshots.
|
|
834
|
+
# true = attempt when capable (see command contract).
|
|
835
|
+
screenshots: false
|
|
836
|
+
|
|
837
|
+
# Optional glob lists that make frontend / API-endpoint detection
|
|
838
|
+
# explicit instead of heuristic. Empty (default) = the command uses a
|
|
839
|
+
# light path/extension heuristic and fails open (no false enrichment
|
|
840
|
+
# when the surface is ambiguous). Populate to pin your project's paths,
|
|
841
|
+
# e.g. ui_paths: ["resources/views/**", "src/pages/**"].
|
|
842
|
+
ui_paths: []
|
|
843
|
+
api_paths: []
|
|
844
|
+
|
|
764
845
|
# --- Memory ---
|
|
765
846
|
#
|
|
766
847
|
# Engineering memory consolidation behaviour. Memory is entirely
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
# Agent config — tool-scope leftovers (ADR-020 § global-only consumer
|
|
2
2
|
# install). After the Phase-5 migration the entire `.augment/`, `.claude/`,
|
|
3
3
|
# `.cursor/` trees live under `~/.event4u/agent-config/`; consumers carry
|
|
4
|
-
# only `agents/overrides
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# directories stay tracked (not listed here). Pre-Phase-5 entries kept
|
|
8
|
-
# as a defence-in-depth ignore list for legacy installs that have not
|
|
9
|
-
# migrated yet — Phase 5 unstages them on first migrate run.
|
|
4
|
+
# only `agents/overrides/`. Pre-Phase-5 entries kept as a defence-in-depth
|
|
5
|
+
# ignore list for legacy installs that have not migrated yet — Phase 5
|
|
6
|
+
# unstages them on first migrate run.
|
|
10
7
|
.augment/skills/
|
|
11
8
|
.augment/commands/
|
|
12
9
|
.augment/guidelines/
|
|
@@ -21,6 +18,19 @@
|
|
|
21
18
|
.claude/CLAUDE.md
|
|
22
19
|
.cursor/rules/
|
|
23
20
|
|
|
21
|
+
# Agent config — consumer bridge marker + per-tool project anchors.
|
|
22
|
+
# The bridge marker was retired (ADR-020 amendment 2026-07-13): it carried
|
|
23
|
+
# no project-specific data (global_root resolves from `~/.event4u/agent-config`)
|
|
24
|
+
# yet was committed and rewritten by every developer's install. The global
|
|
25
|
+
# root is now the well-known path. The Windsurf / Cline / Gemini-CLI anchors
|
|
26
|
+
# remain (those tools cannot read user scope) but are gitignored — each
|
|
27
|
+
# developer regenerates them on install; they are never shared. The install
|
|
28
|
+
# deletes any legacy committed marker; this entry keeps a stale one ignored.
|
|
29
|
+
/agents/.event4u-bridge.yml
|
|
30
|
+
.windsurf/agent-config.bridge.yml
|
|
31
|
+
.clinerules/agent-config.bridge.yml
|
|
32
|
+
.gemini/agent-config.bridge.yml
|
|
33
|
+
|
|
24
34
|
# Agent config — Phase 5 migration snapshot (ADR-020). Written by
|
|
25
35
|
# `agent-config migrate-to-global` as a copy-then-move safety net so a
|
|
26
36
|
# migration can be rolled back. Local-only — never commit.
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
_run_checks_no_manifest,
|
|
48
48
|
_scan_foreign,
|
|
49
49
|
ArgparseExit,
|
|
50
|
-
|
|
50
|
+
_is_global_only_consumer,
|
|
51
51
|
STATUS_SYMBOLS,
|
|
52
52
|
} from './cmd_doctor.js';
|
|
53
53
|
|
|
@@ -630,7 +630,7 @@ function main(argv: string[] | null = null): number {
|
|
|
630
630
|
let doctorChecks: Dict[];
|
|
631
631
|
let drift: Record<string, Dict[]> = { missing: [], modified: [], foreign: [], tag_drift: [] };
|
|
632
632
|
if (manifest === null) {
|
|
633
|
-
const bridge_present =
|
|
633
|
+
const bridge_present = _is_global_only_consumer(project_root);
|
|
634
634
|
doctorChecks = _run_checks_no_manifest(project_root, bridge_present, null);
|
|
635
635
|
} else {
|
|
636
636
|
const [records, known] = _collect_manifest_entries(project_root, manifest);
|
|
@@ -100,6 +100,12 @@ import {
|
|
|
100
100
|
import * as ai_council_clients from '../ai_council/clients.js';
|
|
101
101
|
import * as ai_council_config from '../ai_council/config.js';
|
|
102
102
|
import { review_gate_doctor_signal } from '../ai_team/review_gate.js';
|
|
103
|
+
import {
|
|
104
|
+
LEGACY_ALL,
|
|
105
|
+
excludedRuleBasenames,
|
|
106
|
+
ruleScopeFromSettings,
|
|
107
|
+
type RuleScope,
|
|
108
|
+
} from '../../install/rule_scope.js';
|
|
103
109
|
|
|
104
110
|
type Dict = Record<string, unknown>;
|
|
105
111
|
|
|
@@ -738,6 +744,7 @@ const CHECK_IDS = [
|
|
|
738
744
|
'surface-state',
|
|
739
745
|
'hook-wiring',
|
|
740
746
|
'stale-orphans',
|
|
747
|
+
'rule-scope-drift',
|
|
741
748
|
'manifest-integrity',
|
|
742
749
|
'lockfile-freshness',
|
|
743
750
|
'bridge-drift',
|
|
@@ -764,6 +771,7 @@ const GLOBAL_CHECK_IDS: ReadonlySet<string> = new Set([
|
|
|
764
771
|
'surface-state',
|
|
765
772
|
'hook-wiring',
|
|
766
773
|
'stale-orphans',
|
|
774
|
+
'rule-scope-drift',
|
|
767
775
|
'mcp-mode',
|
|
768
776
|
'mcp-beta-readiness',
|
|
769
777
|
'offline-readiness',
|
|
@@ -783,8 +791,20 @@ const MANIFEST_REQUIRED_CHECK_IDS: ReadonlySet<string> = new Set([
|
|
|
783
791
|
'unsupported-combos',
|
|
784
792
|
]);
|
|
785
793
|
|
|
786
|
-
/**
|
|
787
|
-
|
|
794
|
+
/**
|
|
795
|
+
* Signals a set-up global-only consumer. The `.event4u-bridge.yml` marker was
|
|
796
|
+
* retired (ADR-020 amendment 2026-07-13); the durable project-side signal is
|
|
797
|
+
* the install-mode marker (written on every install) or the `agents/overrides/`
|
|
798
|
+
* scaffold. Either is sufficient.
|
|
799
|
+
*/
|
|
800
|
+
const INSTALL_MODE_MARKER_RELATIVE = 'agents/.agent-state/install-mode.txt';
|
|
801
|
+
|
|
802
|
+
function _is_global_only_consumer(project_root: string): boolean {
|
|
803
|
+
return (
|
|
804
|
+
isFile(path.join(project_root, INSTALL_MODE_MARKER_RELATIVE)) ||
|
|
805
|
+
isDir(path.join(project_root, 'agents', 'overrides'))
|
|
806
|
+
);
|
|
807
|
+
}
|
|
788
808
|
|
|
789
809
|
/** Repair targets that `--repair <id>` accepts. */
|
|
790
810
|
const REPAIR_IDS = ['wizard-state'] as const;
|
|
@@ -1091,15 +1111,16 @@ function _check_global_binary(project_root: string): Dict {
|
|
|
1091
1111
|
remedy: 'agent-config upgrade (refresh the global install + plugin)',
|
|
1092
1112
|
};
|
|
1093
1113
|
}
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1114
|
+
const consumer_note = _is_global_only_consumer(project_root)
|
|
1115
|
+
? ''
|
|
1116
|
+
: ' · no project install marker';
|
|
1096
1117
|
return {
|
|
1097
1118
|
id: 'global-binary',
|
|
1098
1119
|
status: 'ok',
|
|
1099
|
-
message: `on PATH (${binary}); version ${binary_v || 'unknown'}${
|
|
1100
|
-
remedy: !
|
|
1120
|
+
message: `on PATH (${binary}); version ${binary_v || 'unknown'}${consumer_note}`,
|
|
1121
|
+
remedy: !consumer_note
|
|
1101
1122
|
? ''
|
|
1102
|
-
: 'agent-config refresh --project (scaffold
|
|
1123
|
+
: 'agent-config refresh --project (scaffold agents/overrides/)',
|
|
1103
1124
|
};
|
|
1104
1125
|
}
|
|
1105
1126
|
|
|
@@ -1590,6 +1611,130 @@ function _check_stale_orphans(): Dict {
|
|
|
1590
1611
|
};
|
|
1591
1612
|
}
|
|
1592
1613
|
|
|
1614
|
+
/**
|
|
1615
|
+
* Per-file rule projection trees a consumer install writes (basenames match
|
|
1616
|
+
* the shipped `dist/agent-src/rules` tree). The first one that exists and
|
|
1617
|
+
* holds ≥ 1 `.md` rule is the surface we diff against the consumer's scope.
|
|
1618
|
+
*/
|
|
1619
|
+
const _RULE_PROJECTION_DIRS: readonly string[] = [
|
|
1620
|
+
'.augment/rules',
|
|
1621
|
+
'.windsurf/rules',
|
|
1622
|
+
'.cursor/rules',
|
|
1623
|
+
'.clinerules',
|
|
1624
|
+
];
|
|
1625
|
+
|
|
1626
|
+
/**
|
|
1627
|
+
* Rule-scope drift (road-to-request-scoped-rule-load, 9.0 consumer flip).
|
|
1628
|
+
*
|
|
1629
|
+
* After the 9.0 default flip to consumer-scoped rule projection, a consumer
|
|
1630
|
+
* whose settings now exclude maintainer-only workspaces may still carry a
|
|
1631
|
+
* pre-flip FULL/GLOBAL rule projection on disk. This check diffs the scope
|
|
1632
|
+
* the consumer's settings imply against the rules actually projected and
|
|
1633
|
+
* reports the leftover set as an ACTIONABLE diff (not a bare boolean).
|
|
1634
|
+
*
|
|
1635
|
+
* The scope-derived expected set reuses the ONE install-time scoping surface
|
|
1636
|
+
* (`rule_scope.ts`), so doctor's diagnosis can never drift from the install
|
|
1637
|
+
* semantics. `excludedRuleBasenames` returns exactly the rules PRESENT in the
|
|
1638
|
+
* projected tree that the current scope says should NOT arrive.
|
|
1639
|
+
*
|
|
1640
|
+
* Degrades gracefully: `skipped` in the agent-config source repo (the in-repo
|
|
1641
|
+
* `.augment/rules` is the full generated projection, not a consumer surface)
|
|
1642
|
+
* and `skipped` when no projected rule tree is present to inspect.
|
|
1643
|
+
*/
|
|
1644
|
+
function _check_rule_scope_drift(project_root: string): Dict {
|
|
1645
|
+
if (_is_source_repo(project_root)) {
|
|
1646
|
+
return {
|
|
1647
|
+
id: 'rule-scope-drift',
|
|
1648
|
+
status: 'skipped',
|
|
1649
|
+
message: 'agent-config source repo — no consumer rule projection to inspect',
|
|
1650
|
+
remedy: '',
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// Consumer projection scope (absent / empty settings → legacy-all: every
|
|
1655
|
+
// rule ships, so nothing is ever out of scope).
|
|
1656
|
+
let scope: RuleScope = LEGACY_ALL;
|
|
1657
|
+
const settings_file = project_settings_path(project_root);
|
|
1658
|
+
if (isFile(settings_file)) {
|
|
1659
|
+
const loaded = yamlSafeLoad(readText0(settings_file));
|
|
1660
|
+
const raw =
|
|
1661
|
+
typeof loaded === 'object' && loaded !== null && !Array.isArray(loaded)
|
|
1662
|
+
? (loaded as Dict)
|
|
1663
|
+
: {};
|
|
1664
|
+
scope = ruleScopeFromSettings(raw);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// First projected rule tree that exists and holds a `.md` rule.
|
|
1668
|
+
let rules_dir: string | null = null;
|
|
1669
|
+
let found = 0;
|
|
1670
|
+
for (const rel of _RULE_PROJECTION_DIRS) {
|
|
1671
|
+
const cand = path.join(project_root, rel);
|
|
1672
|
+
if (!isDir(cand)) {
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
let count = 0;
|
|
1676
|
+
try {
|
|
1677
|
+
for (const name of fs.readdirSync(cand)) {
|
|
1678
|
+
if (name.endsWith('.md') && isFile(path.join(cand, name))) {
|
|
1679
|
+
count += 1;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
} catch {
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
if (count > 0) {
|
|
1686
|
+
rules_dir = cand;
|
|
1687
|
+
found = count;
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
if (rules_dir === null) {
|
|
1692
|
+
return {
|
|
1693
|
+
id: 'rule-scope-drift',
|
|
1694
|
+
status: 'skipped',
|
|
1695
|
+
message: 'no projected rule tree found (.augment/rules, .windsurf/rules, …) — nothing to inspect',
|
|
1696
|
+
remedy: '',
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
let unexpected: string[];
|
|
1701
|
+
try {
|
|
1702
|
+
unexpected = excludedRuleBasenames(rules_dir, scope);
|
|
1703
|
+
} catch (exc) {
|
|
1704
|
+
const rel = relativeTo(rules_dir, project_root) ?? rules_dir;
|
|
1705
|
+
return {
|
|
1706
|
+
id: 'rule-scope-drift',
|
|
1707
|
+
status: 'fail',
|
|
1708
|
+
message: `cannot inspect ${rel}: ${osErrorStr(exc)}`,
|
|
1709
|
+
remedy: 'check read permissions on the projected rule tree',
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
const rel_dir = relativeTo(rules_dir, project_root) ?? rules_dir;
|
|
1714
|
+
if (unexpected.length === 0) {
|
|
1715
|
+
return {
|
|
1716
|
+
id: 'rule-scope-drift',
|
|
1717
|
+
status: 'ok',
|
|
1718
|
+
message: `rule projection matches scope (${found} rules in ${rel_dir})`,
|
|
1719
|
+
remedy: '',
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
const expected = found - unexpected.length;
|
|
1723
|
+
const sample = unexpected.slice(0, 8);
|
|
1724
|
+
const more = unexpected.length - sample.length;
|
|
1725
|
+
const names = sample.join(', ') + (more > 0 ? `, +${more} more` : '');
|
|
1726
|
+
return {
|
|
1727
|
+
id: 'rule-scope-drift',
|
|
1728
|
+
status: 'warn',
|
|
1729
|
+
message:
|
|
1730
|
+
`rule-scope drift in ${rel_dir}: expected ${expected} rules, found ${found} — ` +
|
|
1731
|
+
`${unexpected.length} unexpected (out of scope): ${names}`,
|
|
1732
|
+
remedy:
|
|
1733
|
+
're-run `agent-config init --project` (or `agent-config global`) to re-project rules ' +
|
|
1734
|
+
'under the current scope — these are leftover from a pre-9.0 full/global projection',
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1593
1738
|
function _check_python_runtime(): Dict {
|
|
1594
1739
|
// Post-teardown: the package runtime is TypeScript-on-`tsx`. python3 is no
|
|
1595
1740
|
// longer a runtime dependency, so this check no longer probes for an
|
|
@@ -2525,6 +2670,7 @@ function _run_checks(
|
|
|
2525
2670
|
'surface-state': _check_surface_state,
|
|
2526
2671
|
'hook-wiring': _check_hook_wiring,
|
|
2527
2672
|
'stale-orphans': _check_stale_orphans,
|
|
2673
|
+
'rule-scope-drift': () => _check_rule_scope_drift(project_root),
|
|
2528
2674
|
'manifest-integrity': () => _check_manifest_integrity(manifest),
|
|
2529
2675
|
'lockfile-freshness': () => _check_lockfile_freshness(manifest),
|
|
2530
2676
|
'bridge-drift': () =>
|
|
@@ -2582,8 +2728,8 @@ function _check_bridge_drift_no_manifest(bridge_present: boolean): Dict {
|
|
|
2582
2728
|
id: 'bridge-drift',
|
|
2583
2729
|
status: 'skipped',
|
|
2584
2730
|
message:
|
|
2585
|
-
'no project lockfile and no
|
|
2586
|
-
'not applicable',
|
|
2731
|
+
'no project lockfile and no consumer install marker → drift ' +
|
|
2732
|
+
'check not applicable',
|
|
2587
2733
|
remedy:
|
|
2588
2734
|
'run `agent-config init` (project install) or ' +
|
|
2589
2735
|
'`agent-config refresh --project` (global-only consumer)',
|
|
@@ -2604,6 +2750,7 @@ function _run_checks_no_manifest(
|
|
|
2604
2750
|
'surface-state': _check_surface_state,
|
|
2605
2751
|
'hook-wiring': _check_hook_wiring,
|
|
2606
2752
|
'stale-orphans': _check_stale_orphans,
|
|
2753
|
+
'rule-scope-drift': () => _check_rule_scope_drift(project_root),
|
|
2607
2754
|
'manifest-integrity': () => _skipped_manifest_check('manifest-integrity'),
|
|
2608
2755
|
'lockfile-freshness': () => _skipped_manifest_check('lockfile-freshness'),
|
|
2609
2756
|
'bridge-drift': () => _check_bridge_drift_no_manifest(bridge_present),
|
|
@@ -2647,7 +2794,7 @@ function _run_no_manifest(
|
|
|
2647
2794
|
print(` 📍 project_root: ${project_root} (origin: ${origin})`);
|
|
2648
2795
|
if (bridge_present) {
|
|
2649
2796
|
print(
|
|
2650
|
-
' ℹ️ global-only consumer:
|
|
2797
|
+
' ℹ️ global-only consumer: install marker present, no ' +
|
|
2651
2798
|
'project lockfile (expected under ADR-020)',
|
|
2652
2799
|
);
|
|
2653
2800
|
print(
|
|
@@ -2655,7 +2802,7 @@ function _run_no_manifest(
|
|
|
2655
2802
|
'to project-local distributed tools',
|
|
2656
2803
|
);
|
|
2657
2804
|
} else {
|
|
2658
|
-
eprint(` ⚠️ no project lockfile and no
|
|
2805
|
+
eprint(` ⚠️ no project lockfile and no consumer install marker at ${project_root}`);
|
|
2659
2806
|
eprint(
|
|
2660
2807
|
' run `agent-config init` (project install) or ' +
|
|
2661
2808
|
'`agent-config refresh --project` (global-only consumer)',
|
|
@@ -2998,7 +3145,7 @@ function main(argv: string[] | null = null): number {
|
|
|
2998
3145
|
const manifest_pth = installed_tools.manifest_path(project_root);
|
|
2999
3146
|
const manifest = installed_tools.read_manifest(manifest_pth);
|
|
3000
3147
|
if (manifest === null) {
|
|
3001
|
-
const bridge_present =
|
|
3148
|
+
const bridge_present = _is_global_only_consumer(project_root);
|
|
3002
3149
|
return _run_no_manifest(opts, project_root, origin, bridge_present);
|
|
3003
3150
|
}
|
|
3004
3151
|
|
|
@@ -3099,6 +3246,7 @@ export {
|
|
|
3099
3246
|
_check_mcp_mode,
|
|
3100
3247
|
_check_offline_readiness,
|
|
3101
3248
|
_check_stale_orphans,
|
|
3249
|
+
_check_rule_scope_drift,
|
|
3102
3250
|
_check_python_runtime,
|
|
3103
3251
|
_check_humanizer_runtime,
|
|
3104
3252
|
_check_mcp_beta_readiness,
|
|
@@ -3122,7 +3270,8 @@ export {
|
|
|
3122
3270
|
CHECK_IDS,
|
|
3123
3271
|
GLOBAL_CHECK_IDS,
|
|
3124
3272
|
MANIFEST_REQUIRED_CHECK_IDS,
|
|
3125
|
-
|
|
3273
|
+
INSTALL_MODE_MARKER_RELATIVE,
|
|
3274
|
+
_is_global_only_consumer,
|
|
3126
3275
|
REPAIR_IDS,
|
|
3127
3276
|
MCP_BETA_GATES,
|
|
3128
3277
|
STATUS_SYMBOLS,
|
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
* global root + Claude plugin hooks are rewritten from the
|
|
17
17
|
* currently-installed package. Idempotent: a second run is a no-op diff.
|
|
18
18
|
* - `--project` — refresh the **minimal** project surface ADR-020 permits for
|
|
19
|
-
* a consumer:
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* a consumer: an `agents/overrides/` scaffold and the managed `agents/`
|
|
20
|
+
* block in `.gitignore`, plus cleanup of any legacy
|
|
21
|
+
* `agents/.event4u-bridge.yml` (the marker was retired — ADR-020 amendment
|
|
22
|
+
* 2026-07-13; the global root is resolved from `~/.event4u/agent-config`).
|
|
23
|
+
* Writes **no** distributed content. No wizard.
|
|
22
24
|
*
|
|
23
25
|
* Bare `agent-config refresh` (no scope flag) errors — never a silent global
|
|
24
26
|
* default (council 2026-05-30).
|
|
@@ -39,17 +41,15 @@
|
|
|
39
41
|
* `cannot run <cmd[0]>: <err>` line to stderr and returns 1, mirroring the
|
|
40
42
|
* Python `except OSError`. The runner is injectable for tests.
|
|
41
43
|
* - `_lib.*` / `scripts.install` / `scripts.sync_gitignore` imports resolve to
|
|
42
|
-
* the `.ts` twins. `scripts.install.
|
|
43
|
-
* module-private in the install twin (not exported); it is replicated
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* `user_global_paths.event4u_root`) is replicated alongside, keeping the
|
|
47
|
-
* shared import surface unchanged (same approach as `cmd_update.ts`).
|
|
44
|
+
* the `.ts` twins. `scripts.install._remove_legacy_consumer_bridge_marker`
|
|
45
|
+
* is module-private in the install twin (not exported); it is replicated
|
|
46
|
+
* here — a small `isFile` + `fs.rmSync` guard gated on dev-mode / source
|
|
47
|
+
* repo, matching the install-twin source.
|
|
48
48
|
* - `Path(__file__).resolve().parents[3]` → `path.resolve(_HERE_DIR, '..',
|
|
49
49
|
* '..', '..')`. `.is_file()` / `.is_dir()` / `.exists()` →
|
|
50
50
|
* `_isFile` / `_isDir` / `_exists`. The `packages/` source-marker glob probe
|
|
51
51
|
* becomes a `readdirSync` scan of `packages/` (the source-marker dir name is
|
|
52
|
-
* the `SRC_MARKER_DIR` constant, shared with the
|
|
52
|
+
* the `SRC_MARKER_DIR` constant, shared with the marker-cleanup guard).
|
|
53
53
|
* - `print(..., file=out/err)` → an injectable `OutSink` defaulting to
|
|
54
54
|
* `process.stdout` / `process.stderr`. The Python `out`/`err` kwargs default
|
|
55
55
|
* to `sys.stdout` / `sys.stderr`.
|
|
@@ -60,17 +60,13 @@
|
|
|
60
60
|
* `except ImportError` branch wording.
|
|
61
61
|
*/
|
|
62
62
|
|
|
63
|
-
import * as crypto from 'node:crypto';
|
|
64
63
|
import * as fs from 'node:fs';
|
|
65
|
-
import * as os from 'node:os';
|
|
66
64
|
import * as path from 'node:path';
|
|
67
65
|
import { spawnSync } from 'node:child_process';
|
|
68
66
|
import process from 'node:process';
|
|
69
67
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
70
68
|
|
|
71
|
-
import * as installed_lock from '../_lib/installed_lock.js';
|
|
72
69
|
import * as cli_wrapper from '../_lib/cli_wrapper.js';
|
|
73
|
-
import * as user_global_paths from '../_lib/user_global_paths.js';
|
|
74
70
|
import * as sync_gitignore from '../sync_gitignore.js';
|
|
75
71
|
|
|
76
72
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
@@ -149,103 +145,30 @@ const CONSUMER_BRIDGE_MARKER_RELPATH = path.join('agents', '.event4u-bridge.yml'
|
|
|
149
145
|
*/
|
|
150
146
|
const SRC_MARKER_DIR = '.agent-src.uncondensed';
|
|
151
147
|
|
|
152
|
-
/** `Path.resolve()` — absolute, symlink-resolved where possible. */
|
|
153
|
-
function resolvePath(p: string): string {
|
|
154
|
-
try {
|
|
155
|
-
return fs.realpathSync(path.resolve(p));
|
|
156
|
-
} catch {
|
|
157
|
-
return path.resolve(p);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function _format_global_root_for_marker(global_root: string): string {
|
|
162
|
-
const home = resolvePath(os.homedir());
|
|
163
|
-
const resolved = resolvePath(global_root);
|
|
164
|
-
const rel = path.relative(home, resolved);
|
|
165
|
-
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
166
|
-
return global_root;
|
|
167
|
-
}
|
|
168
|
-
return `~/${rel.split(path.sep).join('/')}`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** `datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")`. */
|
|
172
|
-
function utcStamp(now?: Date): string {
|
|
173
|
-
const d = now ?? new Date();
|
|
174
|
-
const pad = (n: number, w = 2): string => String(n).padStart(w, '0');
|
|
175
|
-
return (
|
|
176
|
-
`${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}` +
|
|
177
|
-
`T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}Z`
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function mkdirp(p: string): void {
|
|
182
|
-
fs.mkdirSync(p, { recursive: true });
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** `tempfile.mkstemp` + write + `os.chmod(0o644)` + `os.replace` (atomic). */
|
|
186
|
-
function atomicWrite0644(target: string, body: string, prefix: string): void {
|
|
187
|
-
const dir = path.dirname(target);
|
|
188
|
-
const tmpName = path.join(
|
|
189
|
-
dir,
|
|
190
|
-
`${prefix}${process.pid}.${crypto.randomBytes(6).toString('hex')}.yml.tmp`,
|
|
191
|
-
);
|
|
192
|
-
let fd: number | null = null;
|
|
193
|
-
try {
|
|
194
|
-
fd = fs.openSync(tmpName, 'wx', 0o644);
|
|
195
|
-
fs.writeFileSync(fd, body, 'utf-8');
|
|
196
|
-
fs.closeSync(fd);
|
|
197
|
-
fd = null;
|
|
198
|
-
fs.chmodSync(tmpName, 0o644);
|
|
199
|
-
fs.renameSync(tmpName, target);
|
|
200
|
-
} catch (err) {
|
|
201
|
-
if (fd !== null) {
|
|
202
|
-
try {
|
|
203
|
-
fs.closeSync(fd);
|
|
204
|
-
} catch {
|
|
205
|
-
/* fd may already be closed */
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
try {
|
|
209
|
-
fs.unlinkSync(tmpName);
|
|
210
|
-
} catch {
|
|
211
|
-
/* best-effort cleanup */
|
|
212
|
-
}
|
|
213
|
-
throw err;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
148
|
|
|
217
149
|
/**
|
|
218
|
-
* Mirrors `install.
|
|
219
|
-
*
|
|
150
|
+
* Mirrors `install._remove_legacy_consumer_bridge_marker` (module-private).
|
|
151
|
+
* The consumer bridge marker was retired (ADR-020 amendment 2026-07-13); the
|
|
152
|
+
* global root is resolved from the well-known `~/.event4u/agent-config` path.
|
|
153
|
+
* `refresh --project` deletes any legacy `agents/.event4u-bridge.yml` a prior
|
|
154
|
+
* install committed. Returns the removed path, or `null` when nothing to do
|
|
155
|
+
* (no marker / dev mode / source repo).
|
|
220
156
|
*/
|
|
221
|
-
function
|
|
157
|
+
function _remove_legacy_consumer_bridge_marker(
|
|
222
158
|
project_root: string,
|
|
223
|
-
installer_version: string,
|
|
224
159
|
env: NodeJS.ProcessEnv | null = null,
|
|
225
|
-
now: Date | null = null,
|
|
226
160
|
): string | null {
|
|
227
161
|
const env_map = env ?? process.env;
|
|
228
162
|
if (env_map['AGENT_CONFIG_DEV_MODE'] === '1') return null;
|
|
229
163
|
if (_isDir(path.join(project_root, SRC_MARKER_DIR))) return null;
|
|
230
164
|
|
|
231
|
-
const global_root_str = _format_global_root_for_marker(
|
|
232
|
-
user_global_paths.event4u_root(env_map),
|
|
233
|
-
);
|
|
234
|
-
const stamp = utcStamp(now ?? undefined);
|
|
235
|
-
|
|
236
|
-
const body =
|
|
237
|
-
'# event4u/agent-config — consumer bridge marker (auto-written).\n' +
|
|
238
|
-
'# Spec: docs/contracts/consumer-bridge.md (event4u-bridge/v1).\n' +
|
|
239
|
-
'# Reader contract: expand ~ against the current $HOME; fail closed\n' +
|
|
240
|
-
'# when global_root is missing on disk; never write back through it.\n' +
|
|
241
|
-
'schema: event4u-bridge/v1\n' +
|
|
242
|
-
`global_root: ${global_root_str}\n` +
|
|
243
|
-
`installed_at: ${stamp}\n` +
|
|
244
|
-
`installer_version: ${installer_version}\n`;
|
|
245
|
-
|
|
246
165
|
const target = path.join(project_root, CONSUMER_BRIDGE_MARKER_RELPATH);
|
|
247
|
-
|
|
248
|
-
|
|
166
|
+
if (!_isFile(target)) return null;
|
|
167
|
+
try {
|
|
168
|
+
fs.rmSync(target);
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
249
172
|
return target;
|
|
250
173
|
}
|
|
251
174
|
|
|
@@ -292,10 +215,10 @@ function _refresh_global(runner: Runner, out: OutSink, err: OutSink): number {
|
|
|
292
215
|
/**
|
|
293
216
|
* True when `project_root` is the agent-config package itself.
|
|
294
217
|
*
|
|
295
|
-
* `
|
|
218
|
+
* `_remove_legacy_consumer_bridge_marker` only guards on a root-level
|
|
296
219
|
* `.agent-src.uncondensed/`, which the monorepo keeps under `packages/<pkg>/`
|
|
297
|
-
* instead — so the narrow guard misses here and would
|
|
298
|
-
*
|
|
220
|
+
* instead — so the narrow guard misses here and would touch the maintainer
|
|
221
|
+
* tree. This broader check (condensed output, packaged
|
|
299
222
|
* source, or the package's own `package.json` name) makes `refresh --project`
|
|
300
223
|
* a no-op in any agent-config checkout. Consumers use dev-mode, not refresh.
|
|
301
224
|
*/
|
|
@@ -350,20 +273,14 @@ function _refresh_project(project_root: string, out: OutSink, err: OutSink): num
|
|
|
350
273
|
return 0;
|
|
351
274
|
}
|
|
352
275
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
'agent-config source repo (or AGENT_CONFIG_DEV_MODE=1). Nothing ' +
|
|
360
|
-
'to scaffold in a maintainer checkout.',
|
|
361
|
-
);
|
|
362
|
-
return 0;
|
|
276
|
+
// Bridge marker retired (ADR-020 amendment 2026-07-13). The global root is
|
|
277
|
+
// resolved from the well-known `~/.event4u/agent-config` path, so there is
|
|
278
|
+
// no per-project marker to scaffold — clean up any legacy one instead.
|
|
279
|
+
const removed = _remove_legacy_consumer_bridge_marker(project_root);
|
|
280
|
+
if (removed !== null) {
|
|
281
|
+
_print(out, `✅ removed legacy bridge marker: ${removed}`);
|
|
363
282
|
}
|
|
364
283
|
|
|
365
|
-
_print(out, `✅ bridge marker: ${marker}`);
|
|
366
|
-
|
|
367
284
|
const overrides = path.join(project_root, 'agents', 'overrides');
|
|
368
285
|
fs.mkdirSync(overrides, { recursive: true });
|
|
369
286
|
const keep = path.join(overrides, 'README.md');
|
|
@@ -372,8 +289,8 @@ function _refresh_project(project_root: string, out: OutSink, err: OutSink): num
|
|
|
372
289
|
keep,
|
|
373
290
|
'# Project overrides\n\n' +
|
|
374
291
|
'Project-local overrides/extensions of shared skills, rules, and ' +
|
|
375
|
-
'commands. The only project-side agent surface ADR-020 permits ' +
|
|
376
|
-
'
|
|
292
|
+
'commands. The only project-side agent surface ADR-020 permits. ' +
|
|
293
|
+
'See the `override-management` skill.\n',
|
|
377
294
|
);
|
|
378
295
|
}
|
|
379
296
|
_print(out, `✅ overrides scaffold: ${overrides}`);
|
|
@@ -543,7 +460,7 @@ export {
|
|
|
543
460
|
_is_source_repo,
|
|
544
461
|
_refresh_project,
|
|
545
462
|
_sync_gitignore,
|
|
546
|
-
|
|
463
|
+
_remove_legacy_consumer_bridge_marker,
|
|
547
464
|
ArgparseExit,
|
|
548
465
|
};
|
|
549
466
|
export type { Args, Runner };
|