@@ -15,90 +15,96 @@ const noHooks = argv.includes('--no-hooks');
1515const noLinkCli = argv . includes ( '--no-link-cli' ) ;
1616const localIndex = argv . indexOf ( '--project-local' ) ;
1717const 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' ) ) ;
1919const 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