feat(live): config globs + drift-heal warning for multi-page projects

Config drift was a real tripwire for projects with static generators: new
HTML files get added, never make it into config.files, silently skip
injection. Two additions.

config.files entries now accept glob patterns (**, *, ?) expanded via
fs.globSync in live-inject. Multi-page projects can write
["public/**/*.html"] once and never maintain the list again. New optional
exclude field filters out matched files (email templates, demo fixtures).
HARD_EXCLUDES of node_modules and .git are enforced regardless of user
config so vendor trees can never receive a tracking script.

live.mjs now runs a drift scan after inject: walks common page-source
roots (public/, src/, app/, pages/) and reports HTML files not covered
by the resolved inject targets. Respects user excludes so intentional
omissions aren't flagged. Output JSON carries configDrift: { orphans,
orphanCount, hint } or null. live.md documents the agent flow for
surfacing drift to users without auto-mutating the config.

Unbundle config.json from the distributable skill: it's a per-project
artifact, not skill code. readSourceFiles now skips any PER_PROJECT_ARTIFACTS
during source scan so build output to .claude/ .cursor/ etc never ships
one project's inject targets to another's install. The per-harness
copies stay gitignored via the existing **/skills/impeccable/scripts/config.json
rule; each consuming project writes its own on first /impeccable live.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-04-23 09:12:56 -07:00
co-authored by Claude Opus 4.7
parent 6846a135ce
commit b0feed06c9
37 changed files with 3049 additions and 86 deletions
+13 -2
View File
@@ -147,11 +147,22 @@ export function readSourceFiles(rootDir) {
}
}
// Read script files if they exist
// Read script files if they exist.
//
// Per-project artifacts (state files that belong to the consuming
// project, not the distributable skill) must be excluded here so
// the build never bundles them into the skill that ships to users.
// - config.json: the live-mode inject-target list for the current
// project. Written by the agent at first /impeccable live; tied
// to the project's filesystem layout.
const PER_PROJECT_ARTIFACTS = new Set(['config.json']);
const scripts = [];
const scriptsDir = path.join(entryPath, 'scripts');
if (fs.existsSync(scriptsDir)) {
const scriptFiles = fs.readdirSync(scriptsDir).filter(f => fs.statSync(path.join(scriptsDir, f)).isFile());
const scriptFiles = fs.readdirSync(scriptsDir).filter(f => {
if (PER_PROJECT_ARTIFACTS.has(f)) return false;
return fs.statSync(path.join(scriptsDir, f)).isFile();
});
for (const scriptFile of scriptFiles) {
const scriptPath = path.join(scriptsDir, scriptFile);
const scriptContent = fs.readFileSync(scriptPath, 'utf-8');