@event4u/agent-config 8.3.0 → 8.4.1
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 +4 -426
- package/.claude-plugin/skills/install-agent-config/SKILL.md +32 -0
- package/CHANGELOG.md +25 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +1 -1
- 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 +1 -1
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +1 -1
- package/dist/mcp/registry-manifest.json +1 -1
- package/docs/contracts/surface-matrix.md +103 -0
- package/package.json +4 -1
- package/src/agent-src/README.md +64 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +497 -0
- package/src/agent-src/scripts/scan-seeder-violations.php +145 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +1087 -0
- package/src/agent-src/templates/AGENTS.md +34 -0
- package/src/agent-src/templates/scripts/README.md +195 -0
- package/src/agent-src/templates/scripts/check_memory.ts +662 -0
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +370 -0
- package/src/agent-src/templates/scripts/historical-bug-patterns.example.yml +84 -0
- package/src/agent-src/templates/scripts/implement_ticket/__main__.ts +27 -0
- package/src/agent-src/templates/scripts/memory_hash.ts +355 -0
- package/src/agent-src/templates/scripts/memory_lookup.ts +1089 -0
- package/src/agent-src/templates/scripts/memory_report.ts +868 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +444 -0
- package/src/agent-src/templates/scripts/memory_status.ts +215 -0
- package/src/agent-src/templates/scripts/ownership-map.example.yml +87 -0
- package/src/agent-src/templates/scripts/pr-risk-config.example.yml +76 -0
- package/src/agent-src/templates/scripts/pr_review_routing.ts +544 -0
- package/src/agent-src/templates/scripts/pr_risk_review.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/boundary.ts +164 -0
- package/src/agent-src/templates/scripts/telemetry/engagement.ts +479 -0
- package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +394 -0
- package/src/agent-src/templates/scripts/telemetry/settings.ts +210 -0
- package/src/agent-src/templates/scripts/telemetry_record.ts +276 -0
- package/src/agent-src/templates/scripts/telemetry_report.ts +211 -0
- package/src/agent-src/templates/scripts/telemetry_status.ts +333 -0
- package/src/agent-src/templates/scripts/tier_usage_report.ts +619 -0
- package/src/agent-src/templates/scripts/work_engine/__main__.ts +14 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +1261 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/user_global_paths.ts +329 -0
- package/src/agent-src/templates/scripts/work_engine/cli.ts +206 -0
- package/src/agent-src/templates/scripts/work_engine/cli_args.ts +249 -0
- package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +225 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/analyze.ts +125 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +189 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +94 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/memory.ts +193 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/plan.ts +267 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +518 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/report.ts +379 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +268 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +258 -0
- package/src/agent-src/templates/scripts/work_engine/directives/index.ts +32 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/contract.ts +243 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/index.ts +108 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +259 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/ui.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/_passthrough.ts +40 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +241 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +506 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +325 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/index.ts +102 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +462 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +474 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +352 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.ts +33 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.ts +213 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/index.ts +111 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.ts +126 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/report.ts +112 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/test.ts +164 -0
- package/src/agent-src/templates/scripts/work_engine/dispatcher.ts +515 -0
- package/src/agent-src/templates/scripts/work_engine/emitters.ts +119 -0
- package/src/agent-src/templates/scripts/work_engine/errors.ts +24 -0
- package/src/agent-src/templates/scripts/work_engine/hook_bootstrap.ts +104 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.ts +189 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.ts +41 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.ts +89 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +193 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +304 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.ts +110 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.ts +118 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/index.ts +17 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +161 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.ts +45 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/trace.ts +134 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/context.ts +94 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/events.ts +58 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/exceptions.ts +85 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/index.ts +27 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/registry.ts +66 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/runner.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +260 -0
- package/src/agent-src/templates/scripts/work_engine/input_builders.ts +260 -0
- package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +466 -0
- package/src/agent-src/templates/scripts/work_engine/migration/v0_to_v1.ts +531 -0
- package/src/agent-src/templates/scripts/work_engine/orchestration.ts +366 -0
- package/src/agent-src/templates/scripts/work_engine/persona_policy.ts +97 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/diff.ts +135 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/file.ts +175 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/prompt.ts +115 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/confidence.ts +415 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +466 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_trace.ts +298 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +444 -0
- package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +252 -0
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +745 -0
- package/src/agent-src/templates/scripts/work_engine/state.ts +1151 -0
- package/src/agent-src/templates/scripts/work_engine/state_io.ts +413 -0
- package/src/config/surface-matrix.yml +213 -0
- package/src/install/atomic.ts +111 -0
- package/src/install/conflict.ts +226 -0
- package/src/install/detect.ts +280 -0
- package/src/install/paths.ts +90 -0
- package/src/install/plan.ts +205 -0
- package/src/install/preflight.ts +210 -0
- package/src/install/selectedTools.ts +52 -0
- package/src/install/toolDetection.ts +120 -0
- package/src/install/txlog.ts +161 -0
- package/src/install/types.ts +175 -0
- package/src/install/wizard-plan.ts +216 -0
- package/src/scripts/_cli/cmd_converge.ts +399 -0
- package/src/scripts/_cli/cmd_doctor.ts +94 -0
- package/src/scripts/_cli/cmd_upgrade.ts +40 -16
- package/src/scripts/_dispatch.bash +13 -0
- package/src/scripts/condense.ts +67 -36
- package/src/scripts/hook_manifest.yaml +16 -7
- package/src/scripts/lint_marketplace.ts +54 -56
- package/src/scripts/lint_surface_matrix.ts +200 -0
- package/src/scripts/prepack-check.mjs +108 -5
- package/src/scripts/surface_probe_hook.ts +231 -0
package/src/scripts/condense.ts
CHANGED
|
@@ -1810,65 +1810,96 @@ export function generate_subagent_host_contexts(): number {
|
|
|
1810
1810
|
return count;
|
|
1811
1811
|
}
|
|
1812
1812
|
|
|
1813
|
+
// Bootstrap shim (road-to-install-path-convergence): the Claude Code plugin
|
|
1814
|
+
// ships ZERO content skills. It carries only hooks/hooks.json (byte-identical
|
|
1815
|
+
// to the managed settings block, so Claude Code dedupes) plus this single
|
|
1816
|
+
// pointer skill whose description names the canonical install command.
|
|
1817
|
+
export const PLUGIN_POINTER_SLUG = 'install-agent-config';
|
|
1818
|
+
export const PLUGIN_POINTER_SKILL_BODY = `---
|
|
1819
|
+
name: install-agent-config
|
|
1820
|
+
description: >-
|
|
1821
|
+
Install or upgrade the event4u agent-config suite. This marketplace plugin
|
|
1822
|
+
is a bootstrap shim — it ships hooks plus this pointer only; ALL skills,
|
|
1823
|
+
rules, and commands are installed via \`npx -y @event4u/agent-config init\`.
|
|
1824
|
+
Use when the user asks to install, set up, or upgrade agent-config, or
|
|
1825
|
+
wonders why this plugin contains no other skills.
|
|
1826
|
+
---
|
|
1827
|
+
|
|
1828
|
+
# Install agent-config
|
|
1829
|
+
|
|
1830
|
+
This plugin is a **bootstrap shim**. It exists so the marketplace listing
|
|
1831
|
+
stays discoverable and existing plugin installs keep working hooks — the
|
|
1832
|
+
content surface (skills, rules, commands, personas) is distributed as a
|
|
1833
|
+
file projection, not through this plugin.
|
|
1834
|
+
|
|
1835
|
+
## Install (canonical, one command)
|
|
1836
|
+
|
|
1837
|
+
\`\`\`bash
|
|
1838
|
+
npx -y @event4u/agent-config init
|
|
1839
|
+
\`\`\`
|
|
1840
|
+
|
|
1841
|
+
The installer writes the full content projection, registers the dispatcher
|
|
1842
|
+
hooks as a managed block in \`~/.claude/settings.json\` (byte-identical to
|
|
1843
|
+
this plugin's hooks, so nothing double-fires), and keeps everything
|
|
1844
|
+
upgradeable via \`agent-config upgrade\`.
|
|
1845
|
+
|
|
1846
|
+
## Already installed both?
|
|
1847
|
+
|
|
1848
|
+
Run \`agent-config doctor\` to check for duplicate surfaces, and
|
|
1849
|
+
\`agent-config converge\` to clean up consented duplicates.
|
|
1850
|
+
`;
|
|
1851
|
+
|
|
1813
1852
|
export function generate_plugin_command_skills(): number {
|
|
1814
1853
|
if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
|
|
1815
1854
|
return 0;
|
|
1816
1855
|
}
|
|
1817
1856
|
_mkdirp(MODULE_STATE.PLUGIN_SKILLS_DIR);
|
|
1818
1857
|
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
);
|
|
1858
|
+
// 1. Materialize the pointer skill (idempotent overwrite-on-drift).
|
|
1859
|
+
const pointer_dir = path.join(MODULE_STATE.PLUGIN_SKILLS_DIR, PLUGIN_POINTER_SLUG);
|
|
1860
|
+
_mkdirp(pointer_dir);
|
|
1861
|
+
const pointer_file = path.join(pointer_dir, 'SKILL.md');
|
|
1862
|
+
if (_isSymlink(pointer_file)) {
|
|
1863
|
+
fs.unlinkSync(pointer_file);
|
|
1826
1864
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
let count = 0;
|
|
1830
|
-
for (const [source_file, slug] of iterCommands()) {
|
|
1831
|
-
if (skill_names.has(slug)) {
|
|
1832
|
-
continue;
|
|
1833
|
-
}
|
|
1834
|
-
current_slugs.add(slug);
|
|
1835
|
-
const skill_dir = path.join(MODULE_STATE.PLUGIN_SKILLS_DIR, slug);
|
|
1836
|
-
_mkdirp(skill_dir);
|
|
1837
|
-
const skill_file = path.join(skill_dir, 'SKILL.md');
|
|
1838
|
-
if (_existsOrSymlink(skill_file)) {
|
|
1839
|
-
fs.unlinkSync(skill_file);
|
|
1840
|
-
}
|
|
1841
|
-
const rel_path = _relativeToPosix(source_file, MODULE_STATE.PROJECT_ROOT);
|
|
1842
|
-
const rel_target = path.join('../../..', rel_path);
|
|
1843
|
-
fs.symlinkSync(rel_target, skill_file);
|
|
1844
|
-
count += 1;
|
|
1865
|
+
if (!_isFile(pointer_file) || fs.readFileSync(pointer_file, 'utf-8') !== PLUGIN_POINTER_SKILL_BODY) {
|
|
1866
|
+
fs.writeFileSync(pointer_file, PLUGIN_POINTER_SKILL_BODY);
|
|
1845
1867
|
}
|
|
1846
1868
|
|
|
1869
|
+
// 2. Prune every other skill dir so the tree converges to shim shape
|
|
1870
|
+
// from any prior state. Removable = every entry is a symlink (the
|
|
1871
|
+
// generated projection shape — SKILL.md and optional evals links into
|
|
1872
|
+
// src/), or the dir holds only a single SKILL.md file (the old sweep's
|
|
1873
|
+
// rule). Dirs with real extra files are never touched.
|
|
1847
1874
|
let removed_dirs = 0;
|
|
1848
1875
|
for (const item of _iterdirSorted(MODULE_STATE.PLUGIN_SKILLS_DIR)) {
|
|
1849
1876
|
if (!_isDirNoFollow(item) || _isSymlink(item)) {
|
|
1850
1877
|
continue;
|
|
1851
1878
|
}
|
|
1852
|
-
if (
|
|
1879
|
+
if (path.basename(item) === PLUGIN_POINTER_SLUG) {
|
|
1853
1880
|
continue;
|
|
1854
1881
|
}
|
|
1855
|
-
const
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1882
|
+
const entries = _iterdirSorted(item);
|
|
1883
|
+
const all_symlinks = entries.length > 0 && entries.every((e) => _isSymlink(e));
|
|
1884
|
+
const single_skill_md =
|
|
1885
|
+
entries.length === 1 &&
|
|
1886
|
+
path.basename(entries[0] as string) === 'SKILL.md' &&
|
|
1887
|
+
_isFile(entries[0] as string);
|
|
1888
|
+
if (all_symlinks || single_skill_md) {
|
|
1889
|
+
for (const e of entries) {
|
|
1890
|
+
fs.unlinkSync(e);
|
|
1862
1891
|
}
|
|
1892
|
+
fs.rmdirSync(item);
|
|
1893
|
+
removed_dirs += 1;
|
|
1863
1894
|
}
|
|
1864
1895
|
}
|
|
1865
1896
|
|
|
1866
|
-
let msg =
|
|
1897
|
+
let msg = ' ✅ Plugin skills: bootstrap shim (1 pointer skill) in .claude-plugin/skills/';
|
|
1867
1898
|
if (removed_dirs) {
|
|
1868
|
-
msg += ` (${removed_dirs}
|
|
1899
|
+
msg += ` (${removed_dirs} content entries pruned)`;
|
|
1869
1900
|
}
|
|
1870
1901
|
info(msg);
|
|
1871
|
-
return
|
|
1902
|
+
return 1;
|
|
1872
1903
|
}
|
|
1873
1904
|
|
|
1874
1905
|
export function generate_persona_symlinks(): number {
|
|
@@ -125,17 +125,26 @@ concerns:
|
|
|
125
125
|
script: src/scripts/wrapper_freshness_hook.ts
|
|
126
126
|
args: []
|
|
127
127
|
fail_closed: false
|
|
128
|
+
# road-to-install-path-convergence Phase 4 — duplicate-surface probe.
|
|
129
|
+
# Reads src/config/surface-matrix.yml, existence-checks declared duplicate
|
|
130
|
+
# classes, nudges ONCE per day (state: agents/runtime/state/
|
|
131
|
+
# surface-probe.json). Fail-open, never blocking; silent when clean or
|
|
132
|
+
# when install.auto_converge is already set.
|
|
133
|
+
surface-probe:
|
|
134
|
+
script: src/scripts/surface_probe_hook.ts
|
|
135
|
+
args: []
|
|
136
|
+
fail_closed: false
|
|
128
137
|
|
|
129
138
|
platforms:
|
|
130
139
|
augment:
|
|
131
|
-
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness]
|
|
140
|
+
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
132
141
|
session_end: [chat-history]
|
|
133
142
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
134
143
|
pre_tool_use: [block-no-verify, rtk-wrap, design-slop]
|
|
135
144
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan]
|
|
136
145
|
|
|
137
146
|
claude:
|
|
138
|
-
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness]
|
|
147
|
+
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
139
148
|
session_end: [chat-history]
|
|
140
149
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
141
150
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
@@ -157,7 +166,7 @@ platforms:
|
|
|
157
166
|
# Decision matrix + upstream blockers tracked in
|
|
158
167
|
# agents/settings/contexts/chat-history-platform-hooks.md § Cowork.
|
|
159
168
|
cowork:
|
|
160
|
-
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness]
|
|
169
|
+
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
161
170
|
session_end: [chat-history]
|
|
162
171
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
163
172
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
@@ -172,7 +181,7 @@ platforms:
|
|
|
172
181
|
# IDE-only — CLI-only users fall back to /checkpoint per
|
|
173
182
|
# agents/settings/contexts/chat-history-platform-hooks.md.
|
|
174
183
|
cursor:
|
|
175
|
-
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness]
|
|
184
|
+
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
176
185
|
session_end: [chat-history]
|
|
177
186
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
178
187
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
@@ -187,7 +196,7 @@ platforms:
|
|
|
187
196
|
# both map to session_start. TaskCancel maps to stop because the
|
|
188
197
|
# session is interrupted with partial state (mirrors Augment Stop).
|
|
189
198
|
cline:
|
|
190
|
-
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness]
|
|
199
|
+
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
191
200
|
session_end: [chat-history]
|
|
192
201
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
193
202
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
@@ -206,7 +215,7 @@ platforms:
|
|
|
206
215
|
# surface to record verification commands; documented limitation).
|
|
207
216
|
# minimal-safe-diff is omitted entirely on Windsurf for the same reason.
|
|
208
217
|
windsurf:
|
|
209
|
-
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, profile-staleness, wrapper-freshness]
|
|
218
|
+
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, profile-staleness, wrapper-freshness, surface-probe]
|
|
210
219
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
211
220
|
user_prompt_submit: [chat-history, verify-before-complete]
|
|
212
221
|
|
|
@@ -221,7 +230,7 @@ platforms:
|
|
|
221
230
|
# turn-check semantics. AfterAgent fires when the agent loop ends
|
|
222
231
|
# — this is our `stop` slot.
|
|
223
232
|
gemini:
|
|
224
|
-
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness]
|
|
233
|
+
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
225
234
|
session_end: [chat-history]
|
|
226
235
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
227
236
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
@@ -6,16 +6,20 @@
|
|
|
6
6
|
* Wave 4b). Mirrors the Python CLI contract exactly: same cwd-relative scan
|
|
7
7
|
* roots, finding messages, stdout output, and exit codes.
|
|
8
8
|
*
|
|
9
|
-
* Validates the Claude Code Plugin Marketplace manifest against the
|
|
10
|
-
*
|
|
9
|
+
* Validates the Claude Code Plugin Marketplace manifest against the
|
|
10
|
+
* BOOTSTRAP-SHIM shape (road-to-install-path-convergence): the plugin ships
|
|
11
|
+
* ONLY hooks plus a single install-pointer skill — content skills are
|
|
12
|
+
* distributed via the npx file projection, never through this plugin.
|
|
11
13
|
*
|
|
12
14
|
* - Required top-level fields: name, owner, metadata, plugins
|
|
13
15
|
* - owner must have name + email
|
|
14
16
|
* - metadata must have description + version
|
|
15
17
|
* - metadata.version must match package.json (single source of truth)
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
*
|
|
18
|
+
* - the union of plugins[].skills[] must be EXACTLY the pointer skill
|
|
19
|
+
* (./.claude-plugin/skills/install-agent-config) — a repopulated
|
|
20
|
+
* content-skill list FAILS by design
|
|
21
|
+
* - the pointer entry must exist on disk and carry a SKILL.md
|
|
22
|
+
* - .claude-plugin/skills/ on disk must contain ONLY the pointer dir
|
|
19
23
|
*
|
|
20
24
|
* Exit codes: 0 = clean, 1 = problems found, 3 = internal error.
|
|
21
25
|
*/
|
|
@@ -26,14 +30,13 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
26
30
|
const ROOT = ".";
|
|
27
31
|
const MARKETPLACE = path.join(ROOT, ".claude-plugin", "marketplace.json");
|
|
28
32
|
const PACKAGE_JSON = path.join(ROOT, "package.json");
|
|
29
|
-
//
|
|
30
|
-
// dist/agent-src/skills
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
];
|
|
33
|
+
// Bootstrap shim: the ONLY skill the plugin may list. Content skills live in
|
|
34
|
+
// the npx file projection (dist/agent-src/skills/ ships in the npm package,
|
|
35
|
+
// not through the marketplace). .claude/skills/ is a gitignored local channel
|
|
36
|
+
// and is intentionally NOT a marketplace source.
|
|
37
|
+
const POINTER_SKILL_ENTRY = "./.claude-plugin/skills/install-agent-config";
|
|
38
|
+
const PLUGIN_SKILLS_DIR = path.join(ROOT, ".claude-plugin", "skills");
|
|
39
|
+
const POINTER_SKILL_DIRNAME = "install-agent-config";
|
|
37
40
|
|
|
38
41
|
type JsonValue =
|
|
39
42
|
| string
|
|
@@ -174,6 +177,18 @@ export function main(): number {
|
|
|
174
177
|
}
|
|
175
178
|
seen.add(p);
|
|
176
179
|
|
|
180
|
+
// Bootstrap-shim invariant: any entry other than the pointer skill is
|
|
181
|
+
// a repopulated content-skill list and must FAIL.
|
|
182
|
+
if (p !== POINTER_SKILL_ENTRY) {
|
|
183
|
+
errors.push(
|
|
184
|
+
`${entry} violates the bootstrap shim: the plugin must not ship ` +
|
|
185
|
+
`content skills (only \`${POINTER_SKILL_ENTRY}\` is allowed), ` +
|
|
186
|
+
`got \`${p}\` — content is distributed via ` +
|
|
187
|
+
"`npx -y @event4u/agent-config init`",
|
|
188
|
+
);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
177
192
|
// Resolve path relative to repo root (strip leading "./" only,
|
|
178
193
|
// NOT every "." and "/" character)
|
|
179
194
|
const rel = removePrefix(p, "./");
|
|
@@ -189,9 +204,7 @@ export function main(): number {
|
|
|
189
204
|
}
|
|
190
205
|
}
|
|
191
206
|
|
|
192
|
-
//
|
|
193
|
-
// sources (dist/agent-src/skills/ + .claude-plugin/skills/) must appear in
|
|
194
|
-
// some plugin's skills[].
|
|
207
|
+
// Shim completeness: the pointer skill must be listed by some plugin.
|
|
195
208
|
const listed = new Set<string>();
|
|
196
209
|
for (const plugin of plugins) {
|
|
197
210
|
if (!isObject(plugin)) {
|
|
@@ -201,43 +214,38 @@ export function main(): number {
|
|
|
201
214
|
if (Array.isArray(skills)) {
|
|
202
215
|
for (const p of skills) {
|
|
203
216
|
if (typeof p === "string") {
|
|
204
|
-
listed.add(
|
|
217
|
+
listed.add(p);
|
|
205
218
|
}
|
|
206
219
|
}
|
|
207
220
|
}
|
|
208
221
|
}
|
|
222
|
+
if (plugins.length > 0 && !listed.has(POINTER_SKILL_ENTRY)) {
|
|
223
|
+
errors.push(
|
|
224
|
+
`bootstrap shim: pointer skill \`${POINTER_SKILL_ENTRY}\` is not ` +
|
|
225
|
+
"listed in any plugin's skills[]",
|
|
226
|
+
);
|
|
227
|
+
}
|
|
209
228
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// source_dir.relative_to(ROOT).as_posix() — ROOT is ".", so this is the
|
|
215
|
-
// dir spelled relative to cwd with POSIX separators.
|
|
216
|
-
const prefix = relPosix(sourceDir, ROOT);
|
|
217
|
-
let entries: fs.Dirent[];
|
|
229
|
+
// Shim disk shape: .claude-plugin/skills/ must contain ONLY the pointer
|
|
230
|
+
// dir — a repopulated symlink tree (e.g. from a stale generator) FAILS.
|
|
231
|
+
if (_exists(PLUGIN_SKILLS_DIR)) {
|
|
232
|
+
let entries: fs.Dirent[] = [];
|
|
218
233
|
try {
|
|
219
|
-
entries = fs.readdirSync(
|
|
234
|
+
entries = fs.readdirSync(PLUGIN_SKILLS_DIR, { withFileTypes: true });
|
|
220
235
|
} catch {
|
|
221
|
-
|
|
236
|
+
/* unreadable → nothing to assert */
|
|
222
237
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
.map((e) =>
|
|
226
|
-
.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const rel = `${prefix}/${d.name}`;
|
|
235
|
-
if (!listed.has(rel)) {
|
|
236
|
-
errors.push(
|
|
237
|
-
"skill exists on disk but is not listed in marketplace.json: " +
|
|
238
|
-
`\`./${rel}\``,
|
|
239
|
-
);
|
|
240
|
-
}
|
|
238
|
+
const extra = entries
|
|
239
|
+
.filter((e) => _isDir(path.join(PLUGIN_SKILLS_DIR, e.name)))
|
|
240
|
+
.map((e) => e.name)
|
|
241
|
+
.filter((name) => name !== POINTER_SKILL_DIRNAME)
|
|
242
|
+
.sort();
|
|
243
|
+
for (const name of extra) {
|
|
244
|
+
errors.push(
|
|
245
|
+
"bootstrap shim: unexpected skill dir on disk (plugin ships no " +
|
|
246
|
+
`content skills): \`.claude-plugin/skills/${name}\` — remove it ` +
|
|
247
|
+
"or re-run the generator",
|
|
248
|
+
);
|
|
241
249
|
}
|
|
242
250
|
}
|
|
243
251
|
|
|
@@ -246,15 +254,9 @@ export function main(): number {
|
|
|
246
254
|
}
|
|
247
255
|
|
|
248
256
|
const pluginCount = plugins.length;
|
|
249
|
-
let skillCount = 0;
|
|
250
|
-
for (const p of plugins) {
|
|
251
|
-
if (isObject(p) && Array.isArray(p["skills"])) {
|
|
252
|
-
skillCount += p["skills"].length;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
257
|
process.stdout.write(
|
|
256
258
|
`✅ marketplace.json (${pluginCount} plugin` +
|
|
257
|
-
`${pluginCount !== 1 ? "s" : ""},
|
|
259
|
+
`${pluginCount !== 1 ? "s" : ""}, bootstrap shim: 1 pointer skill)\n`,
|
|
258
260
|
);
|
|
259
261
|
process.stdout.write(" No issues found.\n");
|
|
260
262
|
return 0;
|
|
@@ -268,10 +270,6 @@ function _isDir(p: string): boolean {
|
|
|
268
270
|
}
|
|
269
271
|
}
|
|
270
272
|
|
|
271
|
-
function relPosix(child: string, base: string): string {
|
|
272
|
-
return path.relative(base, child).split(path.sep).join("/");
|
|
273
|
-
}
|
|
274
|
-
|
|
275
273
|
function _isCliEntry(): boolean {
|
|
276
274
|
if (process.argv[1] === undefined) {
|
|
277
275
|
return false;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Lint src/config/surface-matrix.yml — the machine-checked per-tool
|
|
4
|
+
* canonical-surface inventory (road-to-install-path-convergence Phase 2).
|
|
5
|
+
*
|
|
6
|
+
* Guarantees:
|
|
7
|
+
* - every tool in install.ts::USER_SCOPE_PATHS has a matrix entry, and the
|
|
8
|
+
* matrix declares no tool the installer does not know (set equality)
|
|
9
|
+
* - each entry's scope_path matches USER_SCOPE_PATHS (drift fails)
|
|
10
|
+
* - surface / hooks values come from the closed enums
|
|
11
|
+
* - duplicate.detect.all_of paths are user-scope (~/) or repo-relative
|
|
12
|
+
* (./); repo-relative paths must resolve on disk
|
|
13
|
+
* - a defined duplicate class must carry a converge action, and the
|
|
14
|
+
* claude-code converge command must name the real plugin id (drift guard
|
|
15
|
+
* against _lib/claude_plugin.ts)
|
|
16
|
+
*
|
|
17
|
+
* Exit codes: 0 = clean, 1 = problems found, 3 = internal error.
|
|
18
|
+
*/
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
22
|
+
import YAML from 'yaml';
|
|
23
|
+
|
|
24
|
+
import { USER_SCOPE_PATHS } from './install.js';
|
|
25
|
+
import { CLAUDE_MARKETPLACE_NAME, CLAUDE_PLUGIN_ID } from './_lib/claude_plugin.js';
|
|
26
|
+
|
|
27
|
+
const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
28
|
+
export const MATRIX_PATH = path.join(REPO_ROOT, 'src', 'config', 'surface-matrix.yml');
|
|
29
|
+
|
|
30
|
+
const SURFACE_VALUES = new Set(['projection', 'plugin', 'bundles', 'export-only']);
|
|
31
|
+
const HOOKS_VALUES = new Set(['managed-settings-block', 'settings-hooks-opt-in', 'plugin', 'none']);
|
|
32
|
+
|
|
33
|
+
type JsonObject = Record<string, unknown>;
|
|
34
|
+
|
|
35
|
+
function isObject(v: unknown): v is JsonObject {
|
|
36
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SurfaceMatrixTool {
|
|
40
|
+
surface: string;
|
|
41
|
+
scope_path: string;
|
|
42
|
+
hooks: string;
|
|
43
|
+
duplicate?: {
|
|
44
|
+
description?: string;
|
|
45
|
+
pending_evidence?: string;
|
|
46
|
+
detect?: { all_of?: string[] };
|
|
47
|
+
};
|
|
48
|
+
converge?: { action?: string; command?: string; reaps?: string[] };
|
|
49
|
+
notes?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function load_surface_matrix(matrixPath: string = MATRIX_PATH): Record<string, SurfaceMatrixTool> {
|
|
53
|
+
const raw = YAML.parse(fs.readFileSync(matrixPath, 'utf-8'), { version: '1.1' }) as JsonObject;
|
|
54
|
+
const tools = raw['tools'];
|
|
55
|
+
if (!isObject(tools)) {
|
|
56
|
+
throw new Error('surface-matrix.yml: `tools` must be a mapping');
|
|
57
|
+
}
|
|
58
|
+
return tools as unknown as Record<string, SurfaceMatrixTool>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function main(): number {
|
|
62
|
+
if (!fs.existsSync(MATRIX_PATH)) {
|
|
63
|
+
process.stdout.write(`❌ ${MATRIX_PATH} not found\n`);
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const errors: string[] = [];
|
|
68
|
+
let tools: Record<string, SurfaceMatrixTool>;
|
|
69
|
+
try {
|
|
70
|
+
tools = load_surface_matrix();
|
|
71
|
+
} catch (e) {
|
|
72
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
73
|
+
process.stdout.write(`❌ surface-matrix.yml unreadable: ${msg}\n`);
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Coverage — set equality with the installer's tool inventory.
|
|
78
|
+
const matrixIds = new Set(Object.keys(tools));
|
|
79
|
+
const installerIds = new Set(Object.keys(USER_SCOPE_PATHS));
|
|
80
|
+
for (const id of installerIds) {
|
|
81
|
+
if (!matrixIds.has(id)) {
|
|
82
|
+
errors.push(`tool \`${id}\` is in USER_SCOPE_PATHS but has no surface-matrix entry`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const id of matrixIds) {
|
|
86
|
+
if (!installerIds.has(id)) {
|
|
87
|
+
errors.push(`tool \`${id}\` is in the surface matrix but not in USER_SCOPE_PATHS`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const [id, entry] of Object.entries(tools)) {
|
|
92
|
+
const where = `tools.${id}`;
|
|
93
|
+
if (!isObject(entry)) {
|
|
94
|
+
errors.push(`${where} must be a mapping`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!SURFACE_VALUES.has(entry.surface)) {
|
|
98
|
+
errors.push(`${where}.surface \`${String(entry.surface)}\` not in {${[...SURFACE_VALUES].join(', ')}}`);
|
|
99
|
+
}
|
|
100
|
+
if (!HOOKS_VALUES.has(entry.hooks)) {
|
|
101
|
+
errors.push(`${where}.hooks \`${String(entry.hooks)}\` not in {${[...HOOKS_VALUES].join(', ')}}`);
|
|
102
|
+
}
|
|
103
|
+
const expectedPath = USER_SCOPE_PATHS[id];
|
|
104
|
+
if (expectedPath && entry.scope_path !== expectedPath) {
|
|
105
|
+
errors.push(
|
|
106
|
+
`${where}.scope_path \`${String(entry.scope_path)}\` drifted from ` +
|
|
107
|
+
`USER_SCOPE_PATHS \`${expectedPath}\``,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const dup = entry.duplicate;
|
|
112
|
+
if (dup !== undefined) {
|
|
113
|
+
if (!isObject(dup)) {
|
|
114
|
+
errors.push(`${where}.duplicate must be a mapping`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const detect = dup.detect;
|
|
118
|
+
if (detect !== undefined) {
|
|
119
|
+
const allOf = isObject(detect) ? detect['all_of'] : undefined;
|
|
120
|
+
if (!Array.isArray(allOf) || allOf.length === 0) {
|
|
121
|
+
errors.push(`${where}.duplicate.detect.all_of must be a non-empty list`);
|
|
122
|
+
} else {
|
|
123
|
+
for (const p of allOf) {
|
|
124
|
+
if (typeof p !== 'string' || !(p.startsWith('~/') || p.startsWith('./'))) {
|
|
125
|
+
errors.push(
|
|
126
|
+
`${where}.duplicate.detect path \`${String(p)}\` must start with ~/ or ./`,
|
|
127
|
+
);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (p.startsWith('./') && !fs.existsSync(path.join(REPO_ROOT, p.slice(2)))) {
|
|
131
|
+
errors.push(`${where}.duplicate.detect repo path does not resolve: \`${p}\``);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// A defined (non-pending) duplicate class needs a converge action.
|
|
136
|
+
const conv = entry.converge;
|
|
137
|
+
if (!isObject(conv) || typeof conv['command'] !== 'string' || conv['command'] === '') {
|
|
138
|
+
errors.push(`${where} defines a duplicate class but no converge.command`);
|
|
139
|
+
}
|
|
140
|
+
} else if (typeof dup['pending_evidence'] !== 'string') {
|
|
141
|
+
errors.push(
|
|
142
|
+
`${where}.duplicate must carry either detect.all_of or pending_evidence`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Drift guard: the claude-code converge command must name the real
|
|
149
|
+
// plugin id + marketplace name from _lib/claude_plugin.ts.
|
|
150
|
+
const claude = tools['claude-code'];
|
|
151
|
+
const claudeCmd = claude && isObject(claude.converge) ? String(claude.converge['command'] ?? '') : '';
|
|
152
|
+
const expectedRef = `${CLAUDE_PLUGIN_ID}@${CLAUDE_MARKETPLACE_NAME}`;
|
|
153
|
+
if (claudeCmd !== '' && !claudeCmd.includes(expectedRef)) {
|
|
154
|
+
errors.push(
|
|
155
|
+
`tools.claude-code.converge.command must reference \`${expectedRef}\` ` +
|
|
156
|
+
`(got \`${claudeCmd}\`)`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (errors.length > 0) {
|
|
161
|
+
process.stdout.write('❌ surface-matrix.yml has problems:\n');
|
|
162
|
+
for (const e of errors) {
|
|
163
|
+
process.stdout.write(` - ${e}\n`);
|
|
164
|
+
}
|
|
165
|
+
return 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
process.stdout.write(
|
|
169
|
+
`✅ surface-matrix.yml (${matrixIds.size} tools, coverage matches USER_SCOPE_PATHS)\n`,
|
|
170
|
+
);
|
|
171
|
+
process.stdout.write(' No issues found.\n');
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function _isCliEntry(): boolean {
|
|
176
|
+
if (process.argv[1] === undefined) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
180
|
+
if (import.meta.url === argvUrl) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
185
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
186
|
+
return here === argv;
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (_isCliEntry()) {
|
|
193
|
+
try {
|
|
194
|
+
process.exit(main());
|
|
195
|
+
} catch (exc) {
|
|
196
|
+
const msg = exc instanceof Error ? exc.message : String(exc);
|
|
197
|
+
process.stderr.write(`❌ internal error: ${msg}\n`);
|
|
198
|
+
process.exit(3);
|
|
199
|
+
}
|
|
200
|
+
}
|