Skip to content

Commit edd656c

Browse files
remove legacy managed files during install
1 parent 7f2456f commit edd656c

1 file changed

Lines changed: 78 additions & 72 deletions

File tree

install.mjs

Lines changed: 78 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -15,90 +15,96 @@ const noHooks = argv.includes('--no-hooks');
1515
const noLinkCli = argv.includes('--no-link-cli');
1616
const localIndex = argv.indexOf('--project-local');
1717
const homeIndex = argv.indexOf('--commandcode-home');
18-
const commandCodeHome = path.resolve(homeIndex >= 0 && argv[homeIndex+1] ? argv[homeIndex+1] : path.join(os.homedir(), '.commandcode'));
18+
const commandCodeHome = path.resolve(homeIndex >= 0 && argv[homeIndex + 1] ? argv[homeIndex + 1] : path.join(os.homedir(), '.commandcode'));
1919
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
2020

21-
function sha256(data){ return crypto.createHash('sha256').update(data).digest('hex'); }
22-
function walk(dir){ const out=[]; for(const e of fs.readdirSync(dir,{withFileTypes:true})){ const f=path.join(dir,e.name); e.isDirectory()?out.push(...walk(f)):out.push(f);} return out; }
23-
function ensureDir(dir){ if(!dryRun) fs.mkdirSync(dir,{recursive:true}); }
24-
function copyWithPolicy(src,dest,backupRoot,installed,skipped){
25-
const data=fs.readFileSync(src); const rel=path.relative(commandCodeHome,dest).replaceAll('\\','/');
26-
if(fs.existsSync(dest)){
27-
const existing=fs.readFileSync(dest); if(sha256(existing)===sha256(data)){installed.push({path:rel,action:'unchanged'}); return;}
28-
if(!force){skipped.push({path:rel,reason:'conflict; use --force to overwrite'}); return;}
29-
if(!dryRun){const b=path.join(backupRoot,rel); fs.mkdirSync(path.dirname(b),{recursive:true}); fs.copyFileSync(dest,b);}
21+
const LEGACY_ENGINE = [
22+
'docgen/bin/docgen.mjs',
23+
...['discover','analyze','semantics','enterprise','plan','generate','generate-batch','enrich','enrich-batch','audit','audit-batch','fix','update-impact','workspace-synthesis'].map((name) => `docgen/prompts/${name}.md`)
24+
];
25+
const LEGACY_AGENTS = ['doc-discoverer','doc-architect','doc-domain-analyst','doc-enterprise-analyst','doc-planner','doc-writer','doc-auditor','doc-system-analyst'].map((name) => `agents/${name}.md`);
26+
const LEGACY_COMMANDS = ['docgen-discover','docgen-analyze','docgen-fix','docgen-update','docgen-enrich','docgen-quality','docgen-semantics','docgen-preflight','docgen-contract-test','docgen-traceability','docgen-enterprise'].map((name) => `commands/${name}.md`);
27+
const LEGACY_MANAGED = [...LEGACY_ENGINE, ...LEGACY_AGENTS, ...LEGACY_COMMANDS];
28+
29+
function sha256(data) { return crypto.createHash('sha256').update(data).digest('hex'); }
30+
function walk(dir) { const out = []; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const file = path.join(dir, entry.name); entry.isDirectory() ? out.push(...walk(file)) : out.push(file); } return out; }
31+
function ensureDir(dir) { if (!dryRun) fs.mkdirSync(dir, { recursive: true }); }
32+
function copyWithPolicy(src, dest, backupRoot, installed, skipped) {
33+
const data = fs.readFileSync(src); const rel = path.relative(commandCodeHome, dest).replaceAll('\\', '/');
34+
if (fs.existsSync(dest)) {
35+
const existing = fs.readFileSync(dest); if (sha256(existing) === sha256(data)) { installed.push({ path: rel, action: 'unchanged' }); return; }
36+
if (!force) { skipped.push({ path: rel, reason: 'conflict; use --force to overwrite' }); return; }
37+
if (!dryRun) { const backup = path.join(backupRoot, rel); fs.mkdirSync(path.dirname(backup), { recursive: true }); fs.copyFileSync(dest, backup); }
38+
}
39+
console.log(`${dryRun ? '[dry-run] ' : ''}copy ${dest}`);
40+
if (!dryRun) { fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.writeFileSync(dest, data); try { fs.chmodSync(dest, fs.statSync(src).mode); } catch {} }
41+
installed.push({ path: rel, action: fs.existsSync(dest) ? 'updated' : 'created' });
42+
}
43+
function removeLegacy(root, relPaths, backupRoot, installed) {
44+
for (const rel of relPaths) {
45+
const file = path.join(root, rel); if (!fs.existsSync(file) || !fs.statSync(file).isFile()) continue;
46+
console.log(`${dryRun ? '[dry-run] ' : ''}remove legacy ${file}`);
47+
if (!dryRun) { const backup = path.join(backupRoot, 'removed-legacy', rel); fs.mkdirSync(path.dirname(backup), { recursive: true }); fs.copyFileSync(file, backup); fs.rmSync(file, { force: true }); }
48+
installed.push({ path: rel.replaceAll('\\', '/'), action: 'removed-legacy' });
3049
}
31-
console.log(`${dryRun?'[dry-run] ':''}copy ${dest}`);
32-
if(!dryRun){fs.mkdirSync(path.dirname(dest),{recursive:true}); fs.writeFileSync(dest,data); try{fs.chmodSync(dest,fs.statSync(src).mode)}catch{}}
33-
installed.push({path:rel,action:fs.existsSync(dest)?'updated':'created'});
3450
}
35-
function hookCommand(file){ return `node ${JSON.stringify(path.join(commandCodeHome,'docgen','hooks',file))}`; }
36-
function mergeGlobalSettings(backupRoot,installed){
37-
if(noHooks) return;
38-
const dest=path.join(commandCodeHome,'settings.json'); let current={};
39-
if(fs.existsSync(dest)){try{current=JSON.parse(fs.readFileSync(dest,'utf8'))}catch(e){console.error(`Invalid JSON: ${dest}: ${e.message}`);process.exit(2)}; if(!dryRun){const b=path.join(backupRoot,'settings.json');fs.mkdirSync(path.dirname(b),{recursive:true});fs.copyFileSync(dest,b)}}
51+
function hookCommand(file) { return `node ${JSON.stringify(path.join(commandCodeHome, 'docgen', 'hooks', file))}`; }
52+
function mergeGlobalSettings(backupRoot, installed) {
53+
if (noHooks) return;
54+
const dest = path.join(commandCodeHome, 'settings.json'); let current = {};
55+
if (fs.existsSync(dest)) { try { current = JSON.parse(fs.readFileSync(dest, 'utf8')); } catch (error) { console.error(`Invalid JSON: ${dest}: ${error.message}`); process.exit(2); } if (!dryRun) { const backup = path.join(backupRoot, 'settings.json'); fs.mkdirSync(path.dirname(backup), { recursive: true }); fs.copyFileSync(dest, backup); } }
4056
current.hooks ??= {};
41-
const defs={
42-
SessionStart:[{hooks:[{type:'command',command:hookCommand('docgen-session-context.mjs'),timeout:5}]}],
43-
PreToolUse:[
44-
{matcher:'write|edit',hooks:[{type:'command',command:hookCommand('docgen-guard-write-paths.mjs'),timeout:5}]},
45-
{matcher:'read',hooks:[{type:'command',command:hookCommand('docgen-guard-read-paths.mjs'),timeout:5}]},
46-
{matcher:'shell',hooks:[{type:'command',command:hookCommand('docgen-guard-shell.mjs'),timeout:5}]}
57+
const defs = {
58+
SessionStart: [{ hooks: [{ type: 'command', command: hookCommand('docgen-session-context.mjs'), timeout: 5 }] }],
59+
PreToolUse: [
60+
{ matcher: 'write|edit', hooks: [{ type: 'command', command: hookCommand('docgen-guard-write-paths.mjs'), timeout: 5 }] },
61+
{ matcher: 'read', hooks: [{ type: 'command', command: hookCommand('docgen-guard-read-paths.mjs'), timeout: 5 }] },
62+
{ matcher: 'shell', hooks: [{ type: 'command', command: hookCommand('docgen-guard-shell.mjs'), timeout: 5 }] }
4763
],
48-
PostToolUse:[{matcher:'write|edit',hooks:[{type:'command',command:hookCommand('docgen-validate-written-artifact.mjs'),timeout:10}]}]
64+
PostToolUse: [{ matcher: 'write|edit', hooks: [{ type: 'command', command: hookCommand('docgen-validate-written-artifact.mjs'), timeout: 10 }] }]
4965
};
50-
for(const [event,arr] of Object.entries(defs)){
51-
current.hooks[event] ??= [];
52-
const sig=new Set(current.hooks[event].flatMap(d=>(d.hooks??[]).map(h=>`${d.matcher??''}|${h.command??''}`)));
53-
for(const d of arr){const fresh=(d.hooks??[]).filter(h=>!sig.has(`${d.matcher??''}|${h.command??''}`));if(fresh.length) current.hooks[event].push({...d,hooks:fresh});}
66+
for (const [event, definitions] of Object.entries(defs)) {
67+
current.hooks[event] ??= []; const signatures = new Set(current.hooks[event].flatMap((definition) => (definition.hooks ?? []).map((hook) => `${definition.matcher ?? ''}|${hook.command ?? ''}`)));
68+
for (const definition of definitions) { const fresh = (definition.hooks ?? []).filter((hook) => !signatures.has(`${definition.matcher ?? ''}|${hook.command ?? ''}`)); if (fresh.length) current.hooks[event].push({ ...definition, hooks: fresh }); }
5469
}
55-
console.log(`${dryRun?'[dry-run] ':''}merge ${dest}`);
56-
if(!dryRun){fs.mkdirSync(path.dirname(dest),{recursive:true});fs.writeFileSync(dest,JSON.stringify(current,null,2)+'\n')}
57-
installed.push({path:'settings.json',action:'merged-docgen-hooks'});
70+
console.log(`${dryRun ? '[dry-run] ' : ''}merge ${dest}`);
71+
if (!dryRun) { fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.writeFileSync(dest, JSON.stringify(current, null, 2) + '\n'); }
72+
installed.push({ path: 'settings.json', action: 'merged-docgen-hooks' });
5873
}
59-
function installGlobal(){
60-
const template=path.join(here,'global-template'); const backupRoot=path.join(commandCodeHome,'docgen-backup',timestamp); const installed=[]; const skipped=[];
74+
function installGlobal() {
75+
const template = path.join(here, 'global-template'); const backupRoot = path.join(commandCodeHome, 'docgen-backup', timestamp); const installed = []; const skipped = [];
6176
ensureDir(commandCodeHome);
62-
for(const area of ['agents','skills','commands']) for(const src of walk(path.join(template,area))) copyWithPolicy(src,path.join(commandCodeHome,area,path.relative(path.join(template,area),src)),backupRoot,installed,skipped);
63-
for(const src of walk(path.join(template,'docgen'))) copyWithPolicy(src,path.join(commandCodeHome,'docgen',path.relative(path.join(template,'docgen'),src)),backupRoot,installed,skipped);
64-
mergeGlobalSettings(backupRoot,installed);
65-
if(!dryRun){
66-
fs.writeFileSync(path.join(commandCodeHome,'docgen','installation.json'),JSON.stringify({schemaVersion:'1.0',kitVersion:version,scope:'global',installedAt:new Date().toISOString(),commandCodeHome,files:installed,skipped},null,2)+'\n');
67-
if(!noLinkCli){
68-
const npm=process.platform==='win32'?'npm.cmd':'npm';
69-
const link=spawnSync(npm,['link'],{cwd:path.join(commandCodeHome,'docgen'),stdio:'inherit',shell:process.platform==='win32'});
70-
if(link.status!==0) console.warn('WARNING: npm link failed. Use `node ~/.commandcode/docgen/bin/docgen.mjs` or rerun without --no-link-cli after fixing npm.');
71-
}
77+
for (const area of ['agents', 'skills', 'commands']) for (const src of walk(path.join(template, area))) copyWithPolicy(src, path.join(commandCodeHome, area, path.relative(path.join(template, area), src)), backupRoot, installed, skipped);
78+
for (const src of walk(path.join(template, 'docgen'))) copyWithPolicy(src, path.join(commandCodeHome, 'docgen', path.relative(path.join(template, 'docgen'), src)), backupRoot, installed, skipped);
79+
removeLegacy(commandCodeHome, LEGACY_MANAGED, backupRoot, installed);
80+
mergeGlobalSettings(backupRoot, installed);
81+
if (!dryRun) {
82+
fs.writeFileSync(path.join(commandCodeHome, 'docgen', 'installation.json'), JSON.stringify({ schemaVersion: '2.0', kitVersion: version, scope: 'global', installedAt: new Date().toISOString(), commandCodeHome, files: installed, skipped }, null, 2) + '\n');
83+
if (!noLinkCli) { const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const link = spawnSync(npm, ['link'], { cwd: path.join(commandCodeHome, 'docgen'), stdio: 'inherit', shell: process.platform === 'win32' }); if (link.status !== 0) console.warn('WARNING: npm link failed. Use `node ~/.commandcode/docgen/bin/docgen-v2.mjs` or rerun after fixing npm.'); }
7284
}
7385
console.log(`\nInstalled Command Code DocGen Kit ${version} globally into ${commandCodeHome}`);
74-
if(skipped.length){console.log('\nSkipped conflicts:');for(const x of skipped)console.log(`- ${x.path}: ${x.reason}`)}
75-
console.log('\nNext:'); console.log(' cd <repository>'); console.log(' docgen init'); console.log(' docgen doctor'); console.log(' docgen all');
86+
if (skipped.length) { console.log('\nSkipped conflicts:'); for (const item of skipped) console.log(`- ${item.path}: ${item.reason}`); }
87+
console.log('\nNext:'); console.log(' cd <repository>'); console.log(' docgen init # new repository'); console.log(' docgen migrate # existing v1 repository'); console.log(' docgen doctor'); console.log(' docgen all');
7688
}
77-
function installProjectLocal(target){
78-
const template=path.join(here,'project-local-template'); const abs=path.resolve(target); if(!fs.existsSync(abs)||!fs.statSync(abs).isDirectory()){console.error(`Target is not a directory: ${abs}`);process.exit(2)}
79-
const backupRoot=path.join(abs,'.docgen','install-backup',timestamp); const installed=[]; const skipped=[];
80-
function localCopy(src,dest){const data=fs.readFileSync(src);const rel=path.relative(abs,dest).replaceAll('\\','/');if(fs.existsSync(dest)){if(sha256(fs.readFileSync(dest))===sha256(data)){installed.push({path:rel,action:'unchanged'});return}if(rel==='.docgenignore'){skipped.push({path:rel,reason:'preserved user-owned ignore policy'});return}if(!force){skipped.push({path:rel,reason:'conflict; use --force'});return}if(!dryRun){const b=path.join(backupRoot,rel);fs.mkdirSync(path.dirname(b),{recursive:true});fs.copyFileSync(dest,b)}}console.log(`${dryRun?'[dry-run] ':''}copy ${rel}`);if(!dryRun){fs.mkdirSync(path.dirname(dest),{recursive:true});fs.writeFileSync(dest,data)}installed.push({path:rel,action:'copied'})}
81-
for(const src of walk(template)){const rel=path.relative(template,src);if(rel==='AGENTS.md'||rel==='.commandcode/settings.json')continue;localCopy(src,path.join(abs,rel))}
82-
// Install the same current engine used by global mode under the project's .commandcode scope.
83-
const localEngineTemplate=path.join(here,'global-template','docgen');
84-
for(const src of walk(localEngineTemplate)){const rel=path.relative(localEngineTemplate,src);localCopy(src,path.join(abs,'.commandcode','docgen',rel))}
85-
if(!dryRun){
86-
const marker={schemaVersion:'1.0',kitVersion:version,initializedAt:new Date().toISOString(),engineScope:'project-local',engineHome:path.join(abs,'.commandcode','docgen').replaceAll('\\','/'),projectRoot:abs.replaceAll('\\','/')};
87-
fs.mkdirSync(path.join(abs,'.docgen'),{recursive:true});fs.writeFileSync(path.join(abs,'.docgen','project.json'),JSON.stringify(marker,null,2)+'\n');
89+
function installProjectLocal(target) {
90+
const template = path.join(here, 'project-local-template'); const abs = path.resolve(target); if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) { console.error(`Target is not a directory: ${abs}`); process.exit(2); }
91+
const backupRoot = path.join(abs, '.docgen', 'install-backup', timestamp); const installed = []; const skipped = [];
92+
function localCopy(src, dest) { const data = fs.readFileSync(src); const rel = path.relative(abs, dest).replaceAll('\\', '/'); if (fs.existsSync(dest)) { if (sha256(fs.readFileSync(dest)) === sha256(data)) { installed.push({ path: rel, action: 'unchanged' }); return; } if (rel === '.docgenignore') { skipped.push({ path: rel, reason: 'preserved user-owned ignore policy' }); return; } if (!force) { skipped.push({ path: rel, reason: 'conflict; use --force' }); return; } if (!dryRun) { const backup = path.join(backupRoot, rel); fs.mkdirSync(path.dirname(backup), { recursive: true }); fs.copyFileSync(dest, backup); } } console.log(`${dryRun ? '[dry-run] ' : ''}copy ${rel}`); if (!dryRun) { fs.mkdirSync(path.dirname(dest), { recursive: true }); fs.writeFileSync(dest, data); } installed.push({ path: rel, action: 'copied' }); }
93+
for (const src of walk(template)) { const rel = path.relative(template, src); if (rel === 'AGENTS.md' || rel === '.commandcode/settings.json') continue; localCopy(src, path.join(abs, rel)); }
94+
const localEngineTemplate = path.join(here, 'global-template', 'docgen');
95+
for (const src of walk(localEngineTemplate)) { const rel = path.relative(localEngineTemplate, src); localCopy(src, path.join(abs, '.commandcode', 'docgen', rel)); }
96+
removeLegacy(path.join(abs, '.commandcode'), LEGACY_MANAGED, backupRoot, installed);
97+
if (!dryRun) { const marker = { schemaVersion: '2.0', kitVersion: version, initializedAt: new Date().toISOString(), engineScope: 'project-local', engineHome: path.join(abs, '.commandcode', 'docgen').replaceAll('\\', '/'), projectRoot: abs.replaceAll('\\', '/') }; fs.mkdirSync(path.join(abs, '.docgen'), { recursive: true }); fs.writeFileSync(path.join(abs, '.docgen', 'project.json'), JSON.stringify(marker, null, 2) + '\n'); }
98+
const markerStart = '<!-- COMMANDCODE-DOCGEN:START -->', markerEnd = '<!-- COMMANDCODE-DOCGEN:END -->';
99+
const memorySrc = fs.readFileSync(path.join(template, 'AGENTS.md'), 'utf8'); const memoryDest = path.join(abs, 'AGENTS.md'); const existingMemory = fs.existsSync(memoryDest) ? fs.readFileSync(memoryDest, 'utf8') : '';
100+
if (!existingMemory.includes(markerStart) || !existingMemory.includes(markerEnd)) { console.log(`${dryRun ? '[dry-run] ' : ''}${existingMemory ? 'append' : 'create'} ${memoryDest}`); if (!dryRun) fs.writeFileSync(memoryDest, existingMemory.trimEnd() + (existingMemory ? '\n\n' : '') + memorySrc.trim() + '\n'); }
101+
if (!noHooks) {
102+
const settingsDest = path.join(abs, '.commandcode', 'settings.json'); let current = {}; const source = JSON.parse(fs.readFileSync(path.join(template, '.commandcode', 'settings.json'), 'utf8'));
103+
if (fs.existsSync(settingsDest)) { try { current = JSON.parse(fs.readFileSync(settingsDest, 'utf8')); } catch (error) { console.error(`Invalid JSON: ${settingsDest}: ${error.message}`); process.exit(2); } }
104+
current.hooks ??= {}; for (const [event, definitions] of Object.entries(source.hooks ?? {})) { current.hooks[event] ??= []; const signatures = new Set(current.hooks[event].flatMap((definition) => (definition.hooks ?? []).map((hook) => `${definition.matcher ?? ''}|${hook.command ?? ''}`))); for (const definition of definitions) { const fresh = (definition.hooks ?? []).filter((hook) => !signatures.has(`${definition.matcher ?? ''}|${hook.command ?? ''}`)); if (fresh.length) current.hooks[event].push({ ...definition, hooks: fresh }); } }
105+
console.log(`${dryRun ? '[dry-run] ' : ''}merge ${settingsDest}`); if (!dryRun) { fs.mkdirSync(path.dirname(settingsDest), { recursive: true }); fs.writeFileSync(settingsDest, JSON.stringify(current, null, 2) + '\n'); }
88106
}
89-
const markerStart='<!-- COMMANDCODE-DOCGEN:START -->', markerEnd='<!-- COMMANDCODE-DOCGEN:END -->';
90-
const memorySrc=fs.readFileSync(path.join(template,'AGENTS.md'),'utf8'); const memoryDest=path.join(abs,'AGENTS.md');
91-
const existingMemory=fs.existsSync(memoryDest)?fs.readFileSync(memoryDest,'utf8'):'';
92-
if(!existingMemory.includes(markerStart)||!existingMemory.includes(markerEnd)){
93-
console.log(`${dryRun?'[dry-run] ':''}${existingMemory?'append':'create'} ${memoryDest}`);
94-
if(!dryRun){fs.writeFileSync(memoryDest,existingMemory.trimEnd()+(existingMemory?'\n\n':'')+memorySrc.trim()+'\n')}
95-
}
96-
if(!noHooks){
97-
const settingsDest=path.join(abs,'.commandcode','settings.json'); let current={}; const source=JSON.parse(fs.readFileSync(path.join(template,'.commandcode','settings.json'),'utf8'));
98-
if(fs.existsSync(settingsDest)){try{current=JSON.parse(fs.readFileSync(settingsDest,'utf8'))}catch(e){console.error(`Invalid JSON: ${settingsDest}: ${e.message}`);process.exit(2)}}
99-
current.hooks ??= {}; for(const [event,defs] of Object.entries(source.hooks??{})){current.hooks[event]??=[];const sig=new Set(current.hooks[event].flatMap(d=>(d.hooks??[]).map(h=>`${d.matcher??''}|${h.command??''}`)));for(const d of defs){const fresh=(d.hooks??[]).filter(h=>!sig.has(`${d.matcher??''}|${h.command??''}`));if(fresh.length)current.hooks[event].push({...d,hooks:fresh})}}
100-
console.log(`${dryRun?'[dry-run] ':''}merge ${settingsDest}`); if(!dryRun){fs.mkdirSync(path.dirname(settingsDest),{recursive:true});fs.writeFileSync(settingsDest,JSON.stringify(current,null,2)+'\n')}
101-
}
102-
console.log(`\nInstalled self-contained project-local DocGen ${version} into ${abs}`); if(skipped.length){console.log('\nSkipped or preserved files:');for(const x of skipped)console.log(`- ${x.path}: ${x.reason}`)}
107+
console.log(`\nInstalled self-contained project-local DocGen ${version} into ${abs}`); if (skipped.length) { console.log('\nSkipped or preserved files:'); for (const item of skipped) console.log(`- ${item.path}: ${item.reason}`); }
103108
}
104-
if(localIndex>=0){const target=argv[localIndex+1];if(!target){console.error('Usage: node install.mjs --project-local <repository> [--force]');process.exit(2)}installProjectLocal(target)}else installGlobal();
109+
110+
if (localIndex >= 0) { const target = argv[localIndex + 1]; if (!target) { console.error('Usage: node install.mjs --project-local <repository> [--force]'); process.exit(2); } installProjectLocal(target); } else installGlobal();

0 commit comments

Comments
 (0)