@event4u/agent-config 4.9.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 +73 -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/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/scripts/_dispatch.bash
CHANGED
|
@@ -101,8 +101,12 @@ Tier 1 — power-user (release shape, audit, migration):
|
|
|
101
101
|
Usage: explain config | explain rule <name>
|
|
102
102
|
| explain route "<text>"
|
|
103
103
|
Flags: --json | --project=<path>
|
|
104
|
-
migrate One-shot migration off legacy
|
|
105
|
-
|
|
104
|
+
migrate One-shot, opinionated migration off every legacy install /
|
|
105
|
+
state shape — removes composer / npm package entries,
|
|
106
|
+
deletes legacy symlinks + project-local config, migrates
|
|
107
|
+
the v0 work-engine state file, refreshes .gitignore.
|
|
108
|
+
Wizard recreates fresh config. Single flag: --dry-run
|
|
109
|
+
(preview only). Contract: docs/contracts/migrate-command.md
|
|
106
110
|
first-run Guided first-run setup — cost profile, settings, tooling
|
|
107
111
|
keys:install-anthropic Install the Anthropic API key for the AI Council
|
|
108
112
|
(interactive, /dev/tty only, writes ~/.config/agent-config/anthropic.key 0600)
|
|
@@ -138,10 +142,6 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
|
|
|
138
142
|
~/.event4u/agent-config/ (the global-only consumer surface,
|
|
139
143
|
ADR-020). Idempotent; --force overwrites a non-empty global
|
|
140
144
|
file, --dry-run lists intended copies with zero writes.
|
|
141
|
-
migrate-to-global One-shot legacy → global-only migration (Phase 5,
|
|
142
|
-
road-to-global-only-install.md). Copy → verify → move →
|
|
143
|
-
bridge. Runs lint_global_paths.py first. Flags:
|
|
144
|
-
--dry-run, --force, --rollback, --skip-perms-gate.
|
|
145
145
|
hooks:install Install the combined pre-commit hook (roadmap-progress
|
|
146
146
|
+ ADR-013 artefact frontmatter lint).
|
|
147
147
|
(use --print to dump it, --force to overwrite an existing hook)
|
|
@@ -156,8 +156,6 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
|
|
|
156
156
|
Usage: hooks:replay --platform <name> --event <event>
|
|
157
157
|
--payload <path|event-name> [--native-event <native>]
|
|
158
158
|
[--manifest <path>] [--json] [--dry-run]
|
|
159
|
-
migrate-state Migrate a legacy .implement-ticket-state.json file
|
|
160
|
-
to the v1 .work-state.json schema (preserves .bak)
|
|
161
159
|
memory:lookup Retrieve memory entries (text or JSON envelope)
|
|
162
160
|
memory:signal Append a provisional intake signal (memory proposal)
|
|
163
161
|
memory:hash Hash a memory entry (YAML or JSON stdin)
|
|
@@ -187,7 +185,7 @@ EOF
|
|
|
187
185
|
if [[ "$tier" == "0" ]]; then
|
|
188
186
|
cat <<'EOF'
|
|
189
187
|
|
|
190
|
-
(Hidden: 15 Tier-1 +
|
|
188
|
+
(Hidden: 15 Tier-1 + 24 Tier-2 commands. Run `./agent-config --help --tier=1`
|
|
191
189
|
or `--tier=all` to see them. Tier criteria: docs/contracts/command-surface-tiers.md.)
|
|
192
190
|
EOF
|
|
193
191
|
fi
|
|
@@ -245,7 +243,6 @@ Examples (Tier 2):
|
|
|
245
243
|
./agent-config settings:check
|
|
246
244
|
./agent-config hooks:install
|
|
247
245
|
./agent-config hooks:replay --platform augment --event post_tool_use --payload post_tool_use --json
|
|
248
|
-
./agent-config migrate-state
|
|
249
246
|
./agent-config memory:lookup --types domain-invariants --key billing
|
|
250
247
|
./agent-config memory:signal --type architecture-decision --path src/Foo.php --body "…"
|
|
251
248
|
./agent-config memory:check --path agents/memory
|
|
@@ -407,21 +404,6 @@ cmd_work() {
|
|
|
407
404
|
exec env PYTHONPATH="$engine_root" python3 -m work_engine "$@"
|
|
408
405
|
}
|
|
409
406
|
|
|
410
|
-
cmd_migrate_state() {
|
|
411
|
-
require_python3
|
|
412
|
-
local engine_root="$PACKAGE_ROOT/.agent-src/templates/scripts"
|
|
413
|
-
if [[ ! -d "$engine_root/work_engine/migration" ]]; then
|
|
414
|
-
echo "❌ agent-config: work_engine.migration module not found at $engine_root/work_engine/migration" >&2
|
|
415
|
-
echo " Reinstall the package and retry." >&2
|
|
416
|
-
return 1
|
|
417
|
-
fi
|
|
418
|
-
# -W ignore::RuntimeWarning suppresses the known sys.modules notice from
|
|
419
|
-
# `python3 -m pkg.subpkg.module` when the parent package eagerly imports
|
|
420
|
-
# the submodule via its CLI module. The migration is non-invasive and
|
|
421
|
-
# the warning is cosmetic; suppressing here avoids touching the engine.
|
|
422
|
-
exec env PYTHONPATH="$engine_root" python3 -W ignore::RuntimeWarning -m work_engine.migration.v0_to_v1 "$@"
|
|
423
|
-
}
|
|
424
|
-
|
|
425
407
|
cmd_memory_lookup() {
|
|
426
408
|
require_python3
|
|
427
409
|
local script
|
|
@@ -560,26 +542,43 @@ cmd_chat_history_checkpoint() {
|
|
|
560
542
|
cmd_hooks_install() {
|
|
561
543
|
local force=false
|
|
562
544
|
local print_only=false
|
|
545
|
+
local claude_mode=false
|
|
546
|
+
local regen_mode=false
|
|
563
547
|
for arg in "$@"; do
|
|
564
548
|
case "$arg" in
|
|
565
549
|
--force) force=true ;;
|
|
566
550
|
--print) print_only=true ;;
|
|
551
|
+
--claude|--lifecycle) claude_mode=true ;;
|
|
552
|
+
--regen) regen_mode=true ;;
|
|
567
553
|
-h|--help)
|
|
568
554
|
cat <<'HELP'
|
|
569
|
-
agent-config hooks:install — install
|
|
570
|
-
(roadmap-progress + ADR-013 artefact frontmatter lint).
|
|
555
|
+
agent-config hooks:install — install hooks scaffolding.
|
|
571
556
|
|
|
572
|
-
|
|
573
|
-
|
|
557
|
+
Three modes, picked by flag combination:
|
|
558
|
+
|
|
559
|
+
(no flag) Install the legacy .git/hooks/pre-commit gate
|
|
560
|
+
(roadmap-progress + ADR-013 frontmatter lint).
|
|
561
|
+
Default for backwards compatibility.
|
|
574
562
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
563
|
+
--claude Wire Claude Code lifecycle hooks: write
|
|
564
|
+
(--lifecycle alias) .claude/settings.json with the plugin enabled +
|
|
565
|
+
ensure the ./agent-config symlink → scripts/agent-config
|
|
566
|
+
at the consumer root. Idempotent.
|
|
567
|
+
|
|
568
|
+
--regen Provision the roadmap-progress regenerator at
|
|
569
|
+
.augment/scripts/update_roadmap_progress.py
|
|
570
|
+
(canonical path per docs/contracts/hook-architecture-v1.md
|
|
571
|
+
§ Regenerator location). Idempotent.
|
|
572
|
+
|
|
573
|
+
--claude --regen Both. The minimal-viable-scaffolding fix path
|
|
574
|
+
for marketplace-install consumers — see
|
|
575
|
+
road-to-hooks-actually-fire-in-consumers Phase 4.
|
|
576
|
+
|
|
577
|
+
Other flags:
|
|
578
|
+
--print Dump the legacy pre-commit hook script to stdout
|
|
579
|
+
(for manual chaining into husky / lefthook / etc.)
|
|
580
|
+
--force Overwrite an existing .git/hooks/pre-commit (DESTRUCTIVE)
|
|
579
581
|
|
|
580
|
-
--print dump the hook script to stdout (for manual chaining into an
|
|
581
|
-
existing pre-commit script, husky, lefthook, etc.)
|
|
582
|
-
--force overwrite an existing .git/hooks/pre-commit (DESTRUCTIVE)
|
|
583
582
|
HELP
|
|
584
583
|
return 0 ;;
|
|
585
584
|
*)
|
|
@@ -589,6 +588,30 @@ HELP
|
|
|
589
588
|
esac
|
|
590
589
|
done
|
|
591
590
|
|
|
591
|
+
# Phase 4 of road-to-hooks-actually-fire-in-consumers: --claude
|
|
592
|
+
# and --regen are mutually compatible with each other but NOT with
|
|
593
|
+
# the legacy pre-commit mode. Routing:
|
|
594
|
+
if $claude_mode || $regen_mode; then
|
|
595
|
+
local rc=0
|
|
596
|
+
if $claude_mode; then
|
|
597
|
+
_hooks_install_claude_lifecycle || rc=$?
|
|
598
|
+
fi
|
|
599
|
+
if $regen_mode && [[ $rc -eq 0 ]]; then
|
|
600
|
+
_hooks_install_regenerator || rc=$?
|
|
601
|
+
fi
|
|
602
|
+
return $rc
|
|
603
|
+
fi
|
|
604
|
+
|
|
605
|
+
# Default-path guidance for callers who passed no flag at all (Phase 4
|
|
606
|
+
# Step 3 — make the new modes discoverable without breaking the
|
|
607
|
+
# legacy default behaviour).
|
|
608
|
+
if [[ $# -eq 0 ]]; then
|
|
609
|
+
echo "ℹ️ hooks:install: no flag given — installing the legacy" >&2
|
|
610
|
+
echo " .git/hooks/pre-commit gate. Use --claude (or --lifecycle)" >&2
|
|
611
|
+
echo " to wire Claude Code hooks, --regen to install the" >&2
|
|
612
|
+
echo " regenerator. See --help for details." >&2
|
|
613
|
+
fi
|
|
614
|
+
|
|
592
615
|
local hook_src
|
|
593
616
|
hook_src="$(resolve_script ".agent-src/templates/hooks/pre-commit-roadmap-progress" ".augment/templates/hooks/pre-commit-roadmap-progress")" || return 1
|
|
594
617
|
|
|
@@ -630,6 +653,92 @@ HELP
|
|
|
630
653
|
echo " To uninstall: rm $target"
|
|
631
654
|
}
|
|
632
655
|
|
|
656
|
+
# Phase 4 of road-to-hooks-actually-fire-in-consumers — `--claude`
|
|
657
|
+
# (and its `--lifecycle` alias) path. Writes the minimal-viable Claude
|
|
658
|
+
# Code lifecycle wiring: enables the plugin in `.claude/settings.json`
|
|
659
|
+
# (idempotent dict-merge) AND ensures `./agent-config` is executable
|
|
660
|
+
# at the consumer root (symlink → scripts/agent-config in the package).
|
|
661
|
+
_hooks_install_claude_lifecycle() {
|
|
662
|
+
local settings_dir="$CONSUMER_ROOT/.claude"
|
|
663
|
+
local settings_file="$settings_dir/settings.json"
|
|
664
|
+
mkdir -p "$settings_dir"
|
|
665
|
+
|
|
666
|
+
# Idempotent merge using python3 — bash JSON edits are unsafe with
|
|
667
|
+
# nested keys. Falls back to a fresh-write when the file is absent.
|
|
668
|
+
python3 - "$settings_file" <<'PY' || return 1
|
|
669
|
+
import json
|
|
670
|
+
import sys
|
|
671
|
+
from pathlib import Path
|
|
672
|
+
|
|
673
|
+
target = Path(sys.argv[1])
|
|
674
|
+
data = {}
|
|
675
|
+
if target.is_file():
|
|
676
|
+
try:
|
|
677
|
+
data = json.loads(target.read_text(encoding="utf-8"))
|
|
678
|
+
except json.JSONDecodeError as exc:
|
|
679
|
+
print(f"❌ hooks:install --claude: existing {target} is not valid JSON ({exc})", file=sys.stderr)
|
|
680
|
+
sys.exit(1)
|
|
681
|
+
if not isinstance(data, dict):
|
|
682
|
+
print(f"❌ hooks:install --claude: existing {target} is not a JSON object", file=sys.stderr)
|
|
683
|
+
sys.exit(1)
|
|
684
|
+
enabled = data.setdefault("enabledPlugins", {})
|
|
685
|
+
if not isinstance(enabled, dict):
|
|
686
|
+
print(f"❌ hooks:install --claude: enabledPlugins in {target} is not an object", file=sys.stderr)
|
|
687
|
+
sys.exit(1)
|
|
688
|
+
enabled["agent-config@event4u-agent-config"] = True
|
|
689
|
+
target.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
690
|
+
print(f"✅ hooks:install --claude: enabled plugin in {target}")
|
|
691
|
+
PY
|
|
692
|
+
|
|
693
|
+
# Symlink the package's agent-config wrapper into the consumer root.
|
|
694
|
+
# Idempotent — replace stale symlinks; skip if already correct.
|
|
695
|
+
local link="$CONSUMER_ROOT/agent-config"
|
|
696
|
+
local link_target
|
|
697
|
+
# The package script lives at <package_root>/scripts/agent-config.
|
|
698
|
+
# Try to resolve the package root via the dispatcher's own location.
|
|
699
|
+
local package_root
|
|
700
|
+
package_root="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null || echo "${BASH_SOURCE[0]}")")")"
|
|
701
|
+
link_target="$package_root/scripts/agent-config"
|
|
702
|
+
|
|
703
|
+
if [[ ! -e "$link_target" ]]; then
|
|
704
|
+
echo "⚠️ hooks:install --claude: package script not found at $link_target" >&2
|
|
705
|
+
echo " Skipping symlink; the plugin-enable step succeeded." >&2
|
|
706
|
+
return 0
|
|
707
|
+
fi
|
|
708
|
+
|
|
709
|
+
if [[ -L "$link" ]]; then
|
|
710
|
+
local current
|
|
711
|
+
current="$(readlink "$link" 2>/dev/null || true)"
|
|
712
|
+
if [[ "$current" == "$link_target" ]]; then
|
|
713
|
+
echo "✅ hooks:install --claude: ./agent-config symlink already current"
|
|
714
|
+
return 0
|
|
715
|
+
fi
|
|
716
|
+
rm "$link"
|
|
717
|
+
elif [[ -e "$link" ]]; then
|
|
718
|
+
echo "⚠️ hooks:install --claude: ./agent-config exists but is not a symlink — leaving it alone" >&2
|
|
719
|
+
return 0
|
|
720
|
+
fi
|
|
721
|
+
|
|
722
|
+
if ln -s "$link_target" "$link" 2>/dev/null; then
|
|
723
|
+
echo "✅ hooks:install --claude: ./agent-config symlink → $link_target"
|
|
724
|
+
else
|
|
725
|
+
echo "⚠️ hooks:install --claude: could not create ./agent-config symlink" >&2
|
|
726
|
+
return 1
|
|
727
|
+
fi
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
# Phase 4 of road-to-hooks-actually-fire-in-consumers — `--regen` path.
|
|
731
|
+
# Provisions the canonical regenerator via the shared helper module.
|
|
732
|
+
# We invoke the script by path (not module form) so it works from any
|
|
733
|
+
# CONSUMER_ROOT — module form requires the package's `scripts` dir to
|
|
734
|
+
# be on PYTHONPATH which is not the case when invoked from a consumer.
|
|
735
|
+
_hooks_install_regenerator() {
|
|
736
|
+
local package_root
|
|
737
|
+
package_root="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null || echo "${BASH_SOURCE[0]}")")")"
|
|
738
|
+
python3 "$package_root/scripts/_lib/install_regenerator.py" "$CONSUMER_ROOT" "$package_root" 2>&1
|
|
739
|
+
return $?
|
|
740
|
+
}
|
|
741
|
+
|
|
633
742
|
# Wrap the interactive key installers under a stable CLI entry. The shell
|
|
634
743
|
# scripts themselves enforce /dev/tty, 0600, and atomic write — this is
|
|
635
744
|
# pure routing so consumers never have to know the package layout.
|
|
@@ -731,23 +840,14 @@ cmd_settings_check() {
|
|
|
731
840
|
# `agent-config settings:migrate` — lift project-local
|
|
732
841
|
# .agent-settings.yml / .agent-user.yml into ~/.event4u/agent-config/.
|
|
733
842
|
# Phase 2.4 of road-to-global-only-install.md. Read-only on the source —
|
|
734
|
-
# the destructive move step is owned by `migrate
|
|
843
|
+
# the destructive move step is owned by the unified `agent-config migrate`
|
|
844
|
+
# (see docs/contracts/migrate-command.md).
|
|
735
845
|
# Exit 0 success / no-op, 1 non-empty global without --force or parse error.
|
|
736
846
|
cmd_settings_migrate() {
|
|
737
847
|
require_python3
|
|
738
848
|
exec env PYTHONPATH="$PACKAGE_ROOT" python3 -m scripts._cli.cmd_settings_migrate "$@"
|
|
739
849
|
}
|
|
740
850
|
|
|
741
|
-
# `agent-config migrate-to-global` — Phase 5.1 + 5.3 + 5.5 of
|
|
742
|
-
# road-to-global-only-install.md. Order: copy → verify → move → bridge.
|
|
743
|
-
# Runs the lint_global_paths.py permissions gate first (Phase 5.0 / A7).
|
|
744
|
-
# Flags: --dry-run (zero writes), --force (overwrite non-empty global),
|
|
745
|
-
# --rollback (reverse the latest .legacy-pre-global-only/<stamp>/ snapshot).
|
|
746
|
-
cmd_migrate_to_global() {
|
|
747
|
-
require_python3
|
|
748
|
-
exec env PYTHONPATH="$PACKAGE_ROOT" python3 -m scripts._cli.cmd_migrate_to_global "$@"
|
|
749
|
-
}
|
|
750
|
-
|
|
751
851
|
# `agent-config uninstall` — remove bridge markers (project) or lockfile
|
|
752
852
|
# entries (global). Idempotent. Pass `--purge` to also delete deployed
|
|
753
853
|
# content directories under user-scope anchors (destructive). See
|
|
@@ -809,7 +909,6 @@ main() {
|
|
|
809
909
|
first-run) cmd_first_run "$@" ;;
|
|
810
910
|
implement-ticket) cmd_implement_ticket "$@" ;;
|
|
811
911
|
work) cmd_work "$@" ;;
|
|
812
|
-
migrate-state) cmd_migrate_state "$@" ;;
|
|
813
912
|
memory:lookup) cmd_memory_lookup "$@" ;;
|
|
814
913
|
memory:signal) cmd_memory_signal "$@" ;;
|
|
815
914
|
memory:hash) cmd_memory_hash "$@" ;;
|
|
@@ -841,7 +940,6 @@ main() {
|
|
|
841
940
|
validate) cmd_validate "$@" ;;
|
|
842
941
|
settings:check) cmd_settings_check "$@" ;;
|
|
843
942
|
settings:migrate) cmd_settings_migrate "$@" ;;
|
|
844
|
-
migrate-to-global) cmd_migrate_to_global "$@" ;;
|
|
845
943
|
uninstall) cmd_uninstall "$@" ;;
|
|
846
944
|
prune) cmd_prune "$@" ;;
|
|
847
945
|
doctor) cmd_doctor "$@" ;;
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Provision the roadmap-progress regenerator into a consumer.
|
|
2
|
+
|
|
3
|
+
Phase 3 of `road-to-hooks-actually-fire-in-consumers`.
|
|
4
|
+
|
|
5
|
+
The roadmap-progress hook (`scripts/roadmap_progress_hook.py`)
|
|
6
|
+
searches three locations for `update_roadmap_progress.py`. Only the
|
|
7
|
+
**canonical** location is reliable in marketplace-install consumers:
|
|
8
|
+
`.augment/scripts/update_roadmap_progress.py`. This helper pins the
|
|
9
|
+
contract and copies the script idempotently.
|
|
10
|
+
|
|
11
|
+
Canonical paths:
|
|
12
|
+
|
|
13
|
+
| Side | Path |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Source-of-truth (package) | `packages/core/.agent-src.uncondensed/scripts/update_roadmap_progress.py` |
|
|
16
|
+
| Package self-use (dogfooding) | `.agent-src/scripts/update_roadmap_progress.py` (and `.augment/scripts/update_roadmap_progress.py` after `task sync`) |
|
|
17
|
+
| Consumer install target | `<consumer_root>/.augment/scripts/update_roadmap_progress.py` |
|
|
18
|
+
|
|
19
|
+
Used by:
|
|
20
|
+
- `scripts/install.py`'s init / full-install path.
|
|
21
|
+
- `scripts/_dispatch.bash::cmd_hooks_install --regen`.
|
|
22
|
+
|
|
23
|
+
Contract: idempotent; preserves executable bit; never blocks.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import shutil
|
|
28
|
+
import stat
|
|
29
|
+
import sys
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Optional
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
REGENERATOR_REL = "scripts/update_roadmap_progress.py"
|
|
35
|
+
"""Path of the script relative to the package's source-of-truth tree."""
|
|
36
|
+
|
|
37
|
+
CONSUMER_REGENERATOR_REL = ".augment/scripts/update_roadmap_progress.py"
|
|
38
|
+
"""Canonical destination path inside a consumer repo."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def package_source(package_root: Path) -> Optional[Path]:
|
|
42
|
+
"""Resolve the package-side source-of-truth for the regenerator.
|
|
43
|
+
|
|
44
|
+
Searches the package layout in priority order:
|
|
45
|
+
1. `packages/core/.agent-src.uncondensed/scripts/update_roadmap_progress.py`
|
|
46
|
+
2. `.agent-src/scripts/update_roadmap_progress.py` (condensed projection)
|
|
47
|
+
3. `.augment/scripts/update_roadmap_progress.py` (tool projection)
|
|
48
|
+
|
|
49
|
+
Returns the first existing file, or None if none exist (which is
|
|
50
|
+
a misconfigured package and should be a hard error at the call
|
|
51
|
+
site).
|
|
52
|
+
"""
|
|
53
|
+
candidates = [
|
|
54
|
+
package_root / "packages" / "core" / ".agent-src.uncondensed" / REGENERATOR_REL,
|
|
55
|
+
package_root / ".agent-src" / REGENERATOR_REL,
|
|
56
|
+
package_root / ".augment" / REGENERATOR_REL,
|
|
57
|
+
]
|
|
58
|
+
for c in candidates:
|
|
59
|
+
if c.is_file():
|
|
60
|
+
return c
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def consumer_target(consumer_root: Path) -> Path:
|
|
65
|
+
"""Canonical destination path inside the consumer repo."""
|
|
66
|
+
return consumer_root / CONSUMER_REGENERATOR_REL
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def install_regenerator(package_root: Path, consumer_root: Path) -> tuple[bool, str]:
|
|
70
|
+
"""Copy the regenerator into the consumer. Idempotent.
|
|
71
|
+
|
|
72
|
+
Returns (success, message). `success=False` means the call site
|
|
73
|
+
should surface the message; the helper never raises.
|
|
74
|
+
"""
|
|
75
|
+
source = package_source(package_root)
|
|
76
|
+
if source is None:
|
|
77
|
+
return (
|
|
78
|
+
False,
|
|
79
|
+
"regenerator source not found in package "
|
|
80
|
+
"(searched packages/core/.agent-src.uncondensed/, "
|
|
81
|
+
".agent-src/, .augment/)",
|
|
82
|
+
)
|
|
83
|
+
target = consumer_target(consumer_root)
|
|
84
|
+
try:
|
|
85
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
# Idempotent: skip the copy if content is byte-identical.
|
|
87
|
+
if target.exists() and target.read_bytes() == source.read_bytes():
|
|
88
|
+
return (True, f"regenerator already current at {target}")
|
|
89
|
+
shutil.copyfile(source, target)
|
|
90
|
+
# Preserve executable bit so the hook can subprocess-call it.
|
|
91
|
+
mode = target.stat().st_mode
|
|
92
|
+
target.chmod(
|
|
93
|
+
mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
|
|
94
|
+
)
|
|
95
|
+
return (True, f"regenerator installed at {target}")
|
|
96
|
+
except OSError as exc:
|
|
97
|
+
return (False, f"failed to install regenerator: {exc}")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def is_installed(consumer_root: Path) -> bool:
|
|
101
|
+
"""Quick boolean — does the canonical regenerator exist + is executable?"""
|
|
102
|
+
target = consumer_target(consumer_root)
|
|
103
|
+
if not target.is_file():
|
|
104
|
+
return False
|
|
105
|
+
import os
|
|
106
|
+
return os.access(target, os.X_OK)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def main() -> int:
|
|
110
|
+
"""CLI entry point — `python3 -m scripts._lib.install_regenerator <consumer-root>`."""
|
|
111
|
+
if len(sys.argv) < 2:
|
|
112
|
+
print(
|
|
113
|
+
"usage: install_regenerator.py <consumer_root> [<package_root>]",
|
|
114
|
+
file=sys.stderr,
|
|
115
|
+
)
|
|
116
|
+
return 2
|
|
117
|
+
consumer_root = Path(sys.argv[1]).resolve()
|
|
118
|
+
package_root = (
|
|
119
|
+
Path(sys.argv[2]).resolve()
|
|
120
|
+
if len(sys.argv) > 2
|
|
121
|
+
else Path(__file__).resolve().parent.parent.parent
|
|
122
|
+
)
|
|
123
|
+
ok, msg = install_regenerator(package_root, consumer_root)
|
|
124
|
+
print(msg)
|
|
125
|
+
return 0 if ok else 1
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
raise SystemExit(main())
|