@event4u/agent-config 4.8.0 → 5.0.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/.agent-src/commands/implement-ticket.md +5 -4
- package/.agent-src/rules/language-and-tone.md +4 -10
- package/.agent-src/skills/command-routing/SKILL.md +5 -4
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +86 -0
- package/CONTRIBUTING.md +19 -0
- package/README.md +11 -0
- package/dist/cli/registry.js +0 -2
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +5 -5
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/router.json +1 -1671
- package/docs/benchmark.md +20 -8
- package/docs/benchmarks.md +11 -0
- package/docs/contracts/benchmark-corpus-spec.md +31 -3
- package/docs/contracts/command-surface-tiers.md +1 -1
- package/docs/contracts/hook-architecture-v1.md +33 -0
- package/docs/contracts/migrate-command.md +197 -0
- package/docs/contracts/settings-api.md +2 -1
- package/docs/contracts/value-dashboard-spec.md +374 -0
- package/docs/contracts/value-report-schema.md +150 -0
- package/docs/decisions/ADR-031-validation-severity-tiers-and-projection-roundtrip.md +97 -0
- package/docs/decisions/INDEX.md +1 -0
- package/docs/guidelines/agent-infra/installed-tools-manifest.md +6 -3
- package/docs/guidelines/agent-infra/language-and-tone-examples.md +35 -0
- package/docs/migration/v1-to-v2.md +40 -27
- package/docs/value.md +84 -0
- package/package.json +8 -8
- package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
- package/scripts/_cli/cmd_migrate.py +264 -102
- package/scripts/_cli/cmd_settings_migrate.py +2 -1
- package/scripts/_dispatch.bash +147 -49
- package/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
- package/scripts/_lib/install_regenerator.py +129 -0
- package/scripts/_lib/value_ladder.py +599 -0
- package/scripts/_lib/value_report.py +441 -0
- package/scripts/bench_rtk_savings.py +320 -0
- package/scripts/compile_router.py +19 -5
- package/scripts/expected_perms.json +1 -1
- package/scripts/first_run_gate_hook.py +178 -0
- package/scripts/hook_manifest.yaml +16 -7
- package/scripts/hooks/dispatch_hook.py +27 -0
- package/scripts/hooks/dispatch_issues.py +136 -0
- package/scripts/hooks_doctor.py +40 -1
- package/scripts/install.py +25 -21
- package/scripts/inventory_abstraction_budget.py +616 -0
- package/scripts/lint_agents_layout.py +5 -4
- package/scripts/lint_bench_corpus.py +86 -4
- package/scripts/lint_global_paths.py +4 -3
- package/scripts/lint_marketplace_install_completeness.py +188 -0
- package/scripts/lint_value_dashboard.py +218 -0
- package/scripts/render_benchmark_md.py +6 -2
- package/scripts/render_value_md.py +355 -0
- package/scripts/repro/repro_marketplace_install_gap.sh +161 -0
- package/scripts/roadmap_progress_hook.py +23 -0
- package/scripts/router_telemetry.py +470 -0
- package/scripts/validate_frontmatter.py +23 -9
- package/scripts/_cli/cmd_migrate_to_global.py +0 -415
package/docs/benchmark.md
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
## Headline
|
|
6
6
|
|
|
7
|
+
> **Track A confirms surface availability** — a precondition, not an impact metric. For the impact view (cost-ladder + behaviour with vs. without), see [`docs/value.md`](value.md).
|
|
8
|
+
|
|
7
9
|
| Metric | with | without | delta |
|
|
8
10
|
|---|---|---|---|
|
|
9
|
-
| Track A
|
|
10
|
-
| Track B completion-rate |
|
|
11
|
-
| Track B mean wall-time |
|
|
12
|
-
| Track B ask-vs-act ratio |
|
|
11
|
+
| Track A surface-availability | 100.0% | 0.0% | 100.0% _(structural — files present)_ |
|
|
12
|
+
| Track B completion-rate | 0.0% | 0.0% | 0.0% |
|
|
13
|
+
| Track B mean wall-time | 0.00s | 0.00s | 0.00s |
|
|
14
|
+
| Track B ask-vs-act ratio | 0.000 | 0.000 | — |
|
|
13
15
|
|
|
14
16
|
## Track A — Behavioural eval
|
|
15
17
|
|
|
@@ -33,9 +35,19 @@ Per-target presence (sample):
|
|
|
33
35
|
|
|
34
36
|
## Track B — Task completion
|
|
35
37
|
|
|
36
|
-
- Mode:
|
|
38
|
+
- Mode: `dry-run`
|
|
39
|
+
- with → **0.0%** (0/13)
|
|
40
|
+
- without → **0.0%** (0/13)
|
|
41
|
+
|
|
42
|
+
Per-category:
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
| Category | with | without | delta |
|
|
45
|
+
|---|---|---|---|
|
|
46
|
+
| bugfix | 0.0% | 0.0% | 0.0% |
|
|
47
|
+
| feature | 0.0% | 0.0% | 0.0% |
|
|
48
|
+
| refactor | 0.0% | 0.0% | 0.0% |
|
|
49
|
+
| testadd | 0.0% | 0.0% | 0.0% |
|
|
50
|
+
| uiaudit | 0.0% | 0.0% | 0.0% |
|
|
39
51
|
|
|
40
52
|
## Methodology
|
|
41
53
|
|
|
@@ -50,7 +62,7 @@ Cache key for the latest run:
|
|
|
50
62
|
- `claude_cli_version`: `2.1.150 (Claude Code)`
|
|
51
63
|
- `target_shape_hash`: `3f2f67cebfbb5fff`
|
|
52
64
|
|
|
53
|
-
- **Last rendered:** `2026-05-
|
|
65
|
+
- **Last rendered:** `2026-05-28T13:55:30+00:00`
|
|
54
66
|
|
|
55
67
|
## History
|
|
56
68
|
|
|
@@ -62,4 +74,4 @@ Last 5 runs (per corpus):
|
|
|
62
74
|
|
|
63
75
|
### `ab-trackb`
|
|
64
76
|
|
|
65
|
-
|
|
77
|
+
- `2026-05-28T13-47-41Z` → 0.0%
|
package/docs/benchmarks.md
CHANGED
|
@@ -19,6 +19,8 @@ discipline (upstream `5b71c7a`).
|
|
|
19
19
|
|---|---|---|
|
|
20
20
|
| `dev` | `tests/eval/corpus-dev.yaml` | router / engine selection |
|
|
21
21
|
| `telegraph` | `internal/bench/corpora/telegraph/prompts.yaml` | condensation dialect (`vs_raw` + `vs_terse`) |
|
|
22
|
+
| `rtk` | `internal/bench/corpora/rtk/commands.yaml` | rtk CLI-output filtering savings (Phase 2 of `road-to-readable-value-dashboard.md`) |
|
|
23
|
+
| `value` | _derived_ | aggregated dashboard — no own corpus, reads from the others |
|
|
22
24
|
|
|
23
25
|
## Reports — naming and trail
|
|
24
26
|
|
|
@@ -39,11 +41,20 @@ one without the other.
|
|
|
39
41
|
| Edit to `scripts/bench_run.py` `--telegraph` arm | `telegraph` | report refreshed in same PR |
|
|
40
42
|
| Edit to `internal/bench/corpora/telegraph/prompts.yaml` | `telegraph` | report refreshed, version bumped (`telegraph-vN+1`) |
|
|
41
43
|
| Edit to `scripts/_lib/bench_telegraph*.py` | `telegraph` | report refreshed in same PR |
|
|
44
|
+
| Edit to any rung source (frugality / telegraph / rtk / A/B) | `value` | `task value` re-renders `docs/value.md` in same PR |
|
|
45
|
+
| Edit to `internal/bench/corpora/rtk/commands.yaml` | `rtk` | `scripts/bench_rtk_savings.py` refreshed in same PR |
|
|
46
|
+
| Edit to `dist/router.json` or any rule frontmatter `triggers:` | `router-telemetry` | `task value:telemetry:replay` refreshes attribution map; required before any Phase 4 / Phase 5 cut |
|
|
42
47
|
|
|
43
48
|
A PR that touches any of the cadence triggers without refreshing the
|
|
44
49
|
corresponding report is rejected by reviewer convention (no CI gate yet
|
|
45
50
|
— the trigger surface is too small to warrant one).
|
|
46
51
|
|
|
52
|
+
## Cost envelope (`rtk` corpus)
|
|
53
|
+
|
|
54
|
+
8 commands × 2 arms (raw vs. rtk-filtered) = 16 local shell invocations
|
|
55
|
+
per run. Zero API spend — pure local measurement. Wall-time ≈ 5–10 s on
|
|
56
|
+
the maintainer's repo (`scripts/bench_rtk_savings.py --quiet`).
|
|
57
|
+
|
|
47
58
|
## Cost envelope (`telegraph` corpus)
|
|
48
59
|
|
|
49
60
|
10 prompts × 3 arms (`condensed` · `terse-control` · `uncondensed`) = 30
|
|
@@ -45,12 +45,35 @@ corpus_id: <id> # short kebab-case identifier
|
|
|
45
45
|
selection_accuracy_target: 0.60 # 0.0–1.0; runner exits non-zero below
|
|
46
46
|
prompts:
|
|
47
47
|
- id: <bucket>-<NN> # e.g. canonical-01, ambiguous-03
|
|
48
|
-
category: <bucket> # canonical | ambiguous | destructive | long-context
|
|
48
|
+
category: <bucket> # canonical | ambiguous | destructive | long-context | router-coverage
|
|
49
49
|
user_type_candidates: [<slug>, ...] # optional; informational
|
|
50
50
|
language: en # en | de — per language-and-tone
|
|
51
51
|
prompt: "<text>" # the agent-facing prompt
|
|
52
52
|
expected_skills: [<slug>, ...] # ≥ 1 entry; non-empty
|
|
53
53
|
expected_carve_outs: [<slug>, ...] # required when category == destructive
|
|
54
|
+
intended_triggers: [<rule_id>, ...] # router-telemetry attribution — rule ids the
|
|
55
|
+
# corpus author expects to activate AND that the
|
|
56
|
+
# deterministic replay can verify (keyword / phrase
|
|
57
|
+
# / command / path with supplied open_files or
|
|
58
|
+
# command context). Replay checks intended vs
|
|
59
|
+
# observed; drift surfaces as a finding, not
|
|
60
|
+
# silently. OPTIONAL on non-router-coverage corpora.
|
|
61
|
+
# (Council R3 honesty floor — pass-3 onwards.)
|
|
62
|
+
replay_opaque_triggers: [<rule_id>, ...] # rule ids the author expects to fire at
|
|
63
|
+
# RUNTIME but only via an `intent` trigger (or a
|
|
64
|
+
# router coverage gap) the static replay cannot
|
|
65
|
+
# see. Reported separately by the telemetry — NOT
|
|
66
|
+
# counted as missed_intended (would be false drift)
|
|
67
|
+
# nor as unintended_activations. A rule may sit in
|
|
68
|
+
# at most one bucket. router-coverage tasks need at
|
|
69
|
+
# least one of {intended_triggers,
|
|
70
|
+
# replay_opaque_triggers} non-empty.
|
|
71
|
+
open_files: [<path>, ...] # optional — paths the agent has "open" when
|
|
72
|
+
# the prompt fires. Used to drive `path_prefix`
|
|
73
|
+
# and `file_pattern` triggers in router replay.
|
|
74
|
+
command: "<slash-command>" # optional — exact command invoked (e.g.
|
|
75
|
+
# `/roadmap:process-step`). Drives `command:`
|
|
76
|
+
# triggers in router replay.
|
|
54
77
|
rubric: # optional structural assertion
|
|
55
78
|
must_include: ["<phrase>", ...] # all phrases must appear in output
|
|
56
79
|
must_not_include: ["<phrase>", ...]
|
|
@@ -67,12 +90,17 @@ prompts:
|
|
|
67
90
|
| `selection_accuracy_target` outside `[0.0, 1.0]` | `target_out_of_range` | `1.5` |
|
|
68
91
|
| Duplicate `id` across prompts | `duplicate_id` | two `canonical-01` |
|
|
69
92
|
| `id` does not match `^[a-z][a-z0-9-]*-\d{2}$` | `bad_id_format` | `Canonical_1` |
|
|
70
|
-
| `category` not in `{canonical, ambiguous, destructive, long-context}` | `bad_category` | `category: misc` |
|
|
93
|
+
| `category` not in `{canonical, ambiguous, destructive, long-context, router-coverage}` | `bad_category` | `category: misc` |
|
|
71
94
|
| `language` not in `{en, de}` | `bad_language` | `language: fr` |
|
|
72
|
-
| `expected_skills` empty / missing | `empty_expected` | `expected_skills: []` |
|
|
95
|
+
| `expected_skills` empty / missing (**except `category == router-coverage`**, which tests trigger activation, not skill selection) | `empty_expected` | `expected_skills: []` |
|
|
73
96
|
| `expected_skills` references an unknown skill slug | `unknown_skill` | `expected_skills: [imaginary]` |
|
|
74
97
|
| `category == destructive` without `expected_carve_outs` | `missing_carve_out` | — |
|
|
75
98
|
| Prompt text empty / whitespace-only | `empty_prompt` | — |
|
|
99
|
+
| `intended_triggers` / `replay_opaque_triggers` references a rule that doesn't exist in `dist/router.json` | `unknown_intended_trigger` | `intended_triggers: [no-such-rule]` |
|
|
100
|
+
| `intended_triggers` present but not a list | `bad_intended_triggers_shape` | `intended_triggers: foo` |
|
|
101
|
+
| `replay_opaque_triggers` present but not a list | `bad_replay_opaque_triggers_shape` | `replay_opaque_triggers: foo` |
|
|
102
|
+
| Same rule id in both `intended_triggers` and `replay_opaque_triggers` | `trigger_in_both_buckets` | `intended:[x]` + `opaque:[x]` |
|
|
103
|
+
| `category == router-coverage` with **both** `intended_triggers` and `replay_opaque_triggers` empty/absent | `missing_intended_triggers` | — |
|
|
76
104
|
|
|
77
105
|
The linter MUST run with `--quiet` honour per the script-output
|
|
78
106
|
convention and emit one violation per line in non-quiet mode.
|
|
@@ -105,7 +105,7 @@ unless `--tier=all`. Reachable by full name; not advertised.
|
|
|
105
105
|
`context-hygiene:hook`, `hooks:install`, `hooks:status`).
|
|
106
106
|
2. **Internal / programmatic.** Called by other scripts or by the
|
|
107
107
|
work-engine, never typed by a human (`memory:*`,
|
|
108
|
-
`proposal:check`, `refine-ticket:detect`,
|
|
108
|
+
`proposal:check`, `refine-ticket:detect`,
|
|
109
109
|
`telemetry:*`, `mcp:render`, `mcp:check`, `mcp:setup`,
|
|
110
110
|
`mcp:run`, `roadmap:progress-check`).
|
|
111
111
|
3. **Sub-command of a slash orchestrator** — the orchestrator is
|
|
@@ -250,6 +250,39 @@ the flag are listed by `./agent-config hooks:doctor` as not
|
|
|
250
250
|
replay-safe; replay tests assert no `agents/runtime/state/` mutation
|
|
251
251
|
post-invocation.
|
|
252
252
|
|
|
253
|
+
## Regenerator location — canonical path (Phase 3 of `road-to-hooks-actually-fire-in-consumers`)
|
|
254
|
+
|
|
255
|
+
The `roadmap-progress` concern's resolver searches three locations
|
|
256
|
+
for `update_roadmap_progress.py`. The **canonical consumer-side
|
|
257
|
+
location is**:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
<consumer_root>/.augment/scripts/update_roadmap_progress.py
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Rationale:
|
|
264
|
+
|
|
265
|
+
- The auto-generated `agents/roadmaps-progress.md` already cites
|
|
266
|
+
`.augment/scripts/update_roadmap_progress.py` in its header.
|
|
267
|
+
- `install.py`'s existing tool projection lays down `.augment/`
|
|
268
|
+
unconditionally; piggy-backing on that directory means consumers
|
|
269
|
+
do not need a separate "scripts" install step.
|
|
270
|
+
- The other two paths (`.agent-src/scripts/`,
|
|
271
|
+
`.agent-src.uncondensed/scripts/`) only populate in
|
|
272
|
+
source-checkouts of the package itself.
|
|
273
|
+
|
|
274
|
+
Source-of-truth in the package: `packages/core/.agent-src.uncondensed/scripts/update_roadmap_progress.py`.
|
|
275
|
+
Helper that copies source → consumer canonical:
|
|
276
|
+
`scripts/_lib/install_regenerator.py`. Consumed by `install.py` and
|
|
277
|
+
`hooks:install --regen`.
|
|
278
|
+
|
|
279
|
+
The resolver in `scripts/roadmap_progress_hook.py::_resolve_regenerator`
|
|
280
|
+
visits the canonical path FIRST; the other two are fallback for
|
|
281
|
+
maintainer / dev workflows. On `return None` the resolver writes a
|
|
282
|
+
`dispatch-issues.jsonl` entry (Phase 1 contract) with
|
|
283
|
+
`prerequisite_missing` so the user can discover the gap via
|
|
284
|
+
`./agent-config hooks:doctor`.
|
|
285
|
+
|
|
253
286
|
## Stability
|
|
254
287
|
|
|
255
288
|
Beta. Breaking changes between v1 and v2 are allowed in a minor
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# `agent-config migrate` — Behavior Contract
|
|
2
|
+
|
|
3
|
+
> **Status:** active · **Owner:** maintainer (`scripts/_cli/cmd_migrate.py`) · **Opened:** 2026-05-29
|
|
4
|
+
>
|
|
5
|
+
> Source: `road-to-one-migrate-command.md` Phase 1. Locks the union of
|
|
6
|
+
> cleanup actions performed by the unified `./agent-config migrate`
|
|
7
|
+
> command and codifies the **deletion-over-migration** policy: the
|
|
8
|
+
> wizard recreates fresh project config, so legacy project-local state
|
|
9
|
+
> is hard-deleted rather than preserved or relocated.
|
|
10
|
+
|
|
11
|
+
## Design intent
|
|
12
|
+
|
|
13
|
+
One opinionated command runs every cleanup step end-to-end. No flag
|
|
14
|
+
matrix to pick between behaviors, no surprises. The single flag is
|
|
15
|
+
`--dry-run` (preview vs. apply). The legacy three-command surface —
|
|
16
|
+
`migrate`, `migrate-state`, `migrate-to-global` — is collapsed into
|
|
17
|
+
one entry point.
|
|
18
|
+
|
|
19
|
+
Rationale:
|
|
20
|
+
|
|
21
|
+
- **One mental model.** The legacy split forced the user to remember
|
|
22
|
+
which slice each command performed. A single opinionated command
|
|
23
|
+
removes that cognitive load.
|
|
24
|
+
- **Deletion over preservation.** The new wizard
|
|
25
|
+
(`agent-config setup`) recreates fresh project / global config on
|
|
26
|
+
next run. Preserving stale `.agent-settings.yml` only carries
|
|
27
|
+
forward old values the user already chose to leave behind.
|
|
28
|
+
- **No setup-migration.** Project-local config is deleted; global
|
|
29
|
+
config is recreated fresh by the wizard. Different paths into the
|
|
30
|
+
same end state, but the deletion path requires no decisions from
|
|
31
|
+
the agent at migrate time.
|
|
32
|
+
|
|
33
|
+
## Input signals — what counts as "needs migration"
|
|
34
|
+
|
|
35
|
+
The command considers a consumer repo migratable when **any** of
|
|
36
|
+
these are detected. The full set is the disjunction below; any one
|
|
37
|
+
hit triggers the apply path.
|
|
38
|
+
|
|
39
|
+
| # | Signal | Source |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| 1 | `@event4u/agent-config` entry in `package.json` `dependencies` or `devDependencies` | npm install era |
|
|
42
|
+
| 2 | `event4u/agent-config` entry in `composer.json` `require` or `require-dev` | Composer install era |
|
|
43
|
+
| 3 | Managed symlink (`.augment`, `.claude`, `.cursor`, `.clinerules`, `.windsurfrules`) pointing into `vendor/` or `node_modules/` | composer / npm install layout |
|
|
44
|
+
| 4 | `.implement-ticket-state.json` file present at project root (v0 work-engine state) | pre-v1 engine schema |
|
|
45
|
+
| 5 | `.agent-settings.yml` at project root (legacy project-local config) | pre-global-only consumer surface |
|
|
46
|
+
| 6 | `.agent-user.yml` at project root (legacy project-local user prefs) | pre-global-only consumer surface |
|
|
47
|
+
| 7 | `settings/.agent-settings.yml` or `settings/.agent-user.yml` (typed-subdir variant) | pre-global-only typed-subdir layout |
|
|
48
|
+
| 8 | Empty `agent-config/` shell directory at project root | leftover from removed `composer/npm` install |
|
|
49
|
+
|
|
50
|
+
The detector returns "already migrated" (exit 0, no writes) when
|
|
51
|
+
none of the signals fire.
|
|
52
|
+
|
|
53
|
+
## Output state — what the consumer looks like post-migration
|
|
54
|
+
|
|
55
|
+
After `./agent-config migrate` (real apply) returns 0, the consumer
|
|
56
|
+
repo carries **none** of these:
|
|
57
|
+
|
|
58
|
+
- ❌ `@event4u/agent-config` in `package.json` (`dependencies` or
|
|
59
|
+
`devDependencies`); the section is removed if it becomes empty.
|
|
60
|
+
- ❌ `event4u/agent-config` in `composer.json` (`require` or
|
|
61
|
+
`require-dev`); the section is removed if it becomes empty.
|
|
62
|
+
- ❌ Managed symlinks pointing into `vendor/` or `node_modules/`.
|
|
63
|
+
- ❌ `.implement-ticket-state.json` at the project root; if v0
|
|
64
|
+
payload was present, it is migrated to `.work-state.json` and the
|
|
65
|
+
v0 source is renamed `.implement-ticket-state.json.bak`. If no v0
|
|
66
|
+
payload existed (file absent), nothing is written.
|
|
67
|
+
- ❌ Project-root `.agent-settings.yml` (hard-deleted).
|
|
68
|
+
- ❌ Project-root `.agent-user.yml` (hard-deleted).
|
|
69
|
+
- ❌ `settings/.agent-settings.yml` and `settings/.agent-user.yml`
|
|
70
|
+
(hard-deleted; the `settings/` directory is removed if it becomes
|
|
71
|
+
empty).
|
|
72
|
+
- ❌ Empty `agent-config/` shell at the project root.
|
|
73
|
+
|
|
74
|
+
The `.gitignore` block is refreshed to the canonical shape
|
|
75
|
+
documented in `scripts/_cli/cmd_migrate.py::GITIGNORE_NEW_BODY`.
|
|
76
|
+
|
|
77
|
+
## Action order — opinionated, fixed
|
|
78
|
+
|
|
79
|
+
Apply path is deterministic — re-running the same input yields the
|
|
80
|
+
same diff. Order is foundation-first so that earlier steps cannot
|
|
81
|
+
break detection for later ones:
|
|
82
|
+
|
|
83
|
+
1. **Detect** legacy signals from the matrix above; collect every
|
|
84
|
+
action that would fire.
|
|
85
|
+
2. **Strip** `composer.json` / `package.json` package entries
|
|
86
|
+
in-place (preserves sibling keys + 2-space indent + trailing
|
|
87
|
+
newline).
|
|
88
|
+
3. **Purge** managed symlinks whose target points into `vendor/` or
|
|
89
|
+
`node_modules/`. User-managed symlinks pointing elsewhere are
|
|
90
|
+
preserved with a warning.
|
|
91
|
+
4. **Migrate state** — if `.implement-ticket-state.json` carries a
|
|
92
|
+
v0 payload, rewrite to `.work-state.json` and rename the v0
|
|
93
|
+
source to `.implement-ticket-state.json.bak`. If the file is
|
|
94
|
+
absent or already v1-shaped, skip.
|
|
95
|
+
5. **Hard-delete** legacy project-local config files:
|
|
96
|
+
- `.agent-settings.yml` (project root)
|
|
97
|
+
- `.agent-user.yml` (project root)
|
|
98
|
+
- `settings/.agent-settings.yml` (typed-subdir variant)
|
|
99
|
+
- `settings/.agent-user.yml` (typed-subdir variant)
|
|
100
|
+
- `settings/` directory itself if empty after the YAML removals.
|
|
101
|
+
6. **Remove** the empty `agent-config/` shell directory at the
|
|
102
|
+
project root, if present and empty.
|
|
103
|
+
7. **Refresh** the `.gitignore` agent-config managed block to the
|
|
104
|
+
canonical shape.
|
|
105
|
+
8. **Summarize** — print every action taken, one per line, with a
|
|
106
|
+
leading bullet.
|
|
107
|
+
|
|
108
|
+
## `--dry-run` semantics
|
|
109
|
+
|
|
110
|
+
- Same detection + summary as the apply path.
|
|
111
|
+
- **Zero filesystem mutations** — no file created, modified, or
|
|
112
|
+
deleted; no symlink removed; no directory removed.
|
|
113
|
+
- Exit codes match the apply path: `0` for "nothing to migrate"
|
|
114
|
+
and `0` for "would migrate these N actions".
|
|
115
|
+
- Non-zero only on detection errors (unreadable file, invalid JSON
|
|
116
|
+
in `composer.json` / `package.json`, etc.).
|
|
117
|
+
- The summary is prefixed with `would` instead of past-tense verbs
|
|
118
|
+
so log scraping can distinguish dry-run from real runs.
|
|
119
|
+
|
|
120
|
+
## Idempotency contract
|
|
121
|
+
|
|
122
|
+
Re-running on a fully-migrated consumer:
|
|
123
|
+
|
|
124
|
+
1. Detector fires zero hits (every signal in the matrix is absent).
|
|
125
|
+
2. Command prints `✅ already migrated — nothing to do.`
|
|
126
|
+
3. Exits `0`.
|
|
127
|
+
4. **No filesystem mutation occurs** — including on `--dry-run`.
|
|
128
|
+
|
|
129
|
+
A partial migration (e.g., a previous run crashed between steps 3
|
|
130
|
+
and 4) re-runs each remaining step on the next invocation. The
|
|
131
|
+
apply order is chosen so partial state never poisons detection of
|
|
132
|
+
the next pending action.
|
|
133
|
+
|
|
134
|
+
## Excluded — what `migrate` does NOT do
|
|
135
|
+
|
|
136
|
+
These cleanup actions exist elsewhere in the package today but are
|
|
137
|
+
intentionally **outside** the unified `migrate` command:
|
|
138
|
+
|
|
139
|
+
| Action | Where it lives instead | Why excluded |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| Lift project-local YAML into `~/.event4u/agent-config/` | wizard (`agent-config setup`) | New global config is created fresh by the wizard; preserving stale values defeats the deletion-over-migration policy. |
|
|
142
|
+
| Write a fresh `.agent-settings.yml` | wizard (`agent-config setup`) | Same as above — the wizard is the source of truth for new project config. |
|
|
143
|
+
| Run the perms gate (`lint_global_paths.py`) | `agent-config doctor` | Migration deletes project-local state; the perms audit is a separate diagnostic on the global tree. |
|
|
144
|
+
| `.legacy-pre-global-only/<stamp>/` snapshot | (removed) | Snapshot-and-rollback was a `migrate-to-global` semantic. The deletion path needs no snapshot — git history is the rollback surface. |
|
|
145
|
+
| `agents/.event4u-bridge.yml` bridge marker write | `install.py` | Bridge marker is an install-time artefact, not a migration concern. |
|
|
146
|
+
| `settings:migrate` (read-only copy of project YAML into global) | (removed; superseded by wizard) | The read-only copy was a stepping stone for the destructive move. With the deletion policy, neither step survives. |
|
|
147
|
+
|
|
148
|
+
## Exit codes
|
|
149
|
+
|
|
150
|
+
| Code | Meaning |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `0` | Migration complete, or nothing to migrate (already migrated), or `--dry-run` plan computed. |
|
|
153
|
+
| `1` | Detection error — unreadable file, invalid JSON in `composer.json` / `package.json`, work-engine v0 → v1 conversion error. |
|
|
154
|
+
| `2` | Unused (reserved). |
|
|
155
|
+
|
|
156
|
+
The apply path never exits non-zero on a partial-migration recovery
|
|
157
|
+
— a step that fires its predicate is allowed to complete cleanly
|
|
158
|
+
even if a sibling step's predicate is already satisfied.
|
|
159
|
+
|
|
160
|
+
## Test surface
|
|
161
|
+
|
|
162
|
+
`tests/migrate/test_unified_migrate.py` covers, against a fixture
|
|
163
|
+
consumer dir under `tests/fixtures/migrate/`:
|
|
164
|
+
|
|
165
|
+
- **Full apply** — fixture carries every input signal; assert each
|
|
166
|
+
output-state predicate holds post-run; summary lists each action.
|
|
167
|
+
- **`--dry-run`** — same fixture, assert zero filesystem mutations
|
|
168
|
+
(snapshot dir tree before and after; bit-identical).
|
|
169
|
+
- **Idempotency** — run twice; assert second run is the
|
|
170
|
+
"already migrated" no-op.
|
|
171
|
+
|
|
172
|
+
## Rollback
|
|
173
|
+
|
|
174
|
+
Restoring the previous command surface:
|
|
175
|
+
|
|
176
|
+
1. Restore the previous `scripts/_cli/cmd_migrate.py` from git
|
|
177
|
+
history.
|
|
178
|
+
2. Restore `scripts/_cli/cmd_migrate_to_global.py`,
|
|
179
|
+
`cmd_migrate_state()` / `cmd_migrate_to_global()` in
|
|
180
|
+
`scripts/_dispatch.bash`, and the corresponding registry entries
|
|
181
|
+
in `src/cli/registry.ts`.
|
|
182
|
+
3. Delete this contract doc.
|
|
183
|
+
4. Restore `scripts/_cli/cmd_settings_migrate.py` if also removed
|
|
184
|
+
(note: this command was originally cited only as a discussion
|
|
185
|
+
item in Phase 1 cross-check; see "Excluded" table above).
|
|
186
|
+
|
|
187
|
+
## See also
|
|
188
|
+
|
|
189
|
+
- `road-to-one-migrate-command.md` — the roadmap this contract
|
|
190
|
+
realizes.
|
|
191
|
+
- `road-to-global-only-install.md` — the predecessor roadmap that
|
|
192
|
+
shipped `migrate-to-global`; superseded by this contract.
|
|
193
|
+
- `road-to-portable-runtime-and-update-check.md` § P3.5–P3.6 — the
|
|
194
|
+
original `migrate` command (composer / npm cleanup).
|
|
195
|
+
- `scripts/_cli/cmd_migrate.py` — implementation.
|
|
196
|
+
- `.agent-src/templates/scripts/work_engine/migration/v0_to_v1.py`
|
|
197
|
+
— state-file migration helper invoked from step 4.
|
|
@@ -175,7 +175,8 @@ project-local `.agent-user.yml` / `.agent-settings.yml` into
|
|
|
175
175
|
`~/.event4u/agent-config/`. Idempotent — refuses to overwrite a
|
|
176
176
|
non-empty global file without `--force`. Order matches Phase 5
|
|
177
177
|
amendment A2 (`copy → verify`; the destructive `move` step is owned by
|
|
178
|
-
`
|
|
178
|
+
the unified `agent-config migrate` command, not this subcommand — see
|
|
179
|
+
`docs/contracts/migrate-command.md`).
|
|
179
180
|
|
|
180
181
|
Flags:
|
|
181
182
|
|