safeword 0.78.2 → 0.78.4
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/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/hooks.json +5 -5
- package/codex-plugin/skills/bdd/references/SCENARIOS.md +3 -0
- package/codex-plugin/skills/bdd/references/TDD.md +11 -5
- package/codex-plugin/skills/closeout/SKILL.md +24 -16
- package/codex-plugin/skills/finish-review/SKILL.md +4 -3
- package/codex-plugin/skills/lint/SKILL.md +1 -1
- package/codex-plugin/skills/review-spec/SKILL.md +5 -1
- package/codex-plugin/skills/self-review/SKILL.md +2 -2
- package/codex-plugin/skills/ticket-system/SKILL.md +3 -0
- package/dist/{architecture-N5ZC4CDU.js → architecture-TCSDEFKH.js} +9 -9
- package/dist/{architecture-document-UBA5UT4J.js → architecture-document-L2ADWVMV.js} +6 -6
- package/dist/{architecture-monorepo-E4JUYJGV.js → architecture-monorepo-NKOQZBEY.js} +4 -4
- package/dist/{boundary-HLJILY7R.js → boundary-QP2H7C46.js} +6 -6
- package/dist/{chunk-NOTBZCKG.js → chunk-2XR7J5WL.js} +93 -42
- package/dist/chunk-2XR7J5WL.js.map +1 -0
- package/dist/{chunk-4CZS4YJV.js → chunk-3OKZ3X4O.js} +3 -3
- package/dist/{chunk-GZ36UZJG.js → chunk-73TABQRT.js} +10 -10
- package/dist/{chunk-4CKCRZDO.js → chunk-7ULZND56.js} +2 -2
- package/dist/{chunk-34NCOTP5.js → chunk-7XELS325.js} +2 -2
- package/dist/{chunk-USPVNZ63.js → chunk-BZWO3BVD.js} +11 -7
- package/dist/chunk-BZWO3BVD.js.map +1 -0
- package/dist/{chunk-BQTC6YGN.js → chunk-D5ENZR6N.js} +2 -2
- package/dist/{chunk-SME5SU3O.js → chunk-DBXX5K74.js} +258 -48
- package/dist/chunk-DBXX5K74.js.map +1 -0
- package/dist/{chunk-V63TSN3C.js → chunk-FOYGZ4MK.js} +31 -10
- package/dist/chunk-FOYGZ4MK.js.map +1 -0
- package/dist/{chunk-ZRV7TOCX.js → chunk-G5AFQXXW.js} +2 -2
- package/dist/{chunk-AXAGJLMP.js → chunk-G757F3EU.js} +4 -4
- package/dist/{chunk-KXHVFCDT.js → chunk-GFDINP7M.js} +153 -27
- package/dist/chunk-GFDINP7M.js.map +1 -0
- package/dist/{chunk-S2T356DA.js → chunk-HF3DXZEA.js} +7 -10
- package/dist/chunk-HF3DXZEA.js.map +1 -0
- package/dist/{chunk-NSZGI7HK.js → chunk-HNCUIXDY.js} +9 -9
- package/dist/{chunk-NSZGI7HK.js.map → chunk-HNCUIXDY.js.map} +1 -1
- package/dist/{chunk-BSOUMHMQ.js → chunk-IPTM5VE7.js} +101 -1094
- package/dist/chunk-IPTM5VE7.js.map +1 -0
- package/dist/chunk-JBOLNV2L.js +21 -0
- package/dist/chunk-JBOLNV2L.js.map +1 -0
- package/dist/chunk-JSR3HXFT.js +1231 -0
- package/dist/chunk-JSR3HXFT.js.map +1 -0
- package/dist/{chunk-BAVDDAN3.js → chunk-KS2ESRQL.js} +9 -9
- package/dist/chunk-LFC6CMMX.js +136 -0
- package/dist/chunk-LFC6CMMX.js.map +1 -0
- package/dist/{chunk-QTG2EXFT.js → chunk-LUNHU6Z3.js} +2 -2
- package/dist/{chunk-QTG2EXFT.js.map → chunk-LUNHU6Z3.js.map} +1 -1
- package/dist/{chunk-WVDRXD67.js → chunk-MLBZFB5A.js} +1 -1
- package/dist/chunk-MLBZFB5A.js.map +1 -0
- package/dist/{chunk-KFVEPQ7X.js → chunk-MVFHTUQ3.js} +2 -2
- package/dist/{chunk-TJ627SKI.js → chunk-O3GZ77F7.js} +15 -1
- package/dist/chunk-O3GZ77F7.js.map +1 -0
- package/dist/{chunk-Q5KLE6ZF.js → chunk-OKPRQGFG.js} +2 -2
- package/dist/{chunk-QUDIOBHP.js → chunk-PD7ZXRHV.js} +139 -18
- package/dist/chunk-PD7ZXRHV.js.map +1 -0
- package/dist/chunk-QAHJSG53.js +30 -0
- package/dist/chunk-QAHJSG53.js.map +1 -0
- package/dist/{chunk-3SOJBMM6.js → chunk-R6BSRNU4.js} +6 -6
- package/dist/{chunk-3SOJBMM6.js.map → chunk-R6BSRNU4.js.map} +1 -1
- package/dist/{chunk-J7UDLLTK.js → chunk-RQ7RZYNB.js} +39 -25
- package/dist/chunk-RQ7RZYNB.js.map +1 -0
- package/dist/{chunk-664YNAMR.js → chunk-RTFPKJJV.js} +5 -5
- package/dist/{chunk-GKR44BCH.js → chunk-RVDXBHUG.js} +2 -2
- package/dist/chunk-THLEVFEX.js +22 -0
- package/dist/chunk-THLEVFEX.js.map +1 -0
- package/dist/{chunk-A7Z27COA.js → chunk-VFWAZLEF.js} +3 -3
- package/dist/{chunk-I4KBHRZW.js → chunk-VJUPD7JI.js} +7 -7
- package/dist/{chunk-3EJNDIAO.js → chunk-VZ5B2SIO.js} +2 -2
- package/dist/chunk-WAMQTZAH.js +19 -0
- package/dist/chunk-WAMQTZAH.js.map +1 -0
- package/dist/{chunk-FDHJ63FZ.js → chunk-WVA4Q6BX.js} +1 -1
- package/dist/{chunk-FDHJ63FZ.js.map → chunk-WVA4Q6BX.js.map} +1 -1
- package/dist/{chunk-AV5LTEME.js → chunk-YZ5JGDOV.js} +30 -134
- package/dist/chunk-YZ5JGDOV.js.map +1 -0
- package/dist/{cleanup-X556LNWM.js → cleanup-JNLZ7AXA.js} +9 -9
- package/dist/{cleanup-command-RV2Q7G72.js → cleanup-command-MFAYNYRW.js} +15 -15
- package/dist/cli.js +152 -109
- package/dist/cli.js.map +1 -1
- package/dist/{clients-HP23HRN2.js → clients-7AJBKEWF.js} +2 -2
- package/dist/codex-bootstrap-KFNUS63Y.js +252 -0
- package/dist/codex-bootstrap-KFNUS63Y.js.map +1 -0
- package/dist/{codex-hook-IPX3PDXX.js → codex-hook-TNMYVOR7.js} +57 -49
- package/dist/codex-hook-TNMYVOR7.js.map +1 -0
- package/dist/{codify-J5ZLJ3HK.js → codify-RBWBRXQO.js} +11 -8
- package/dist/codify-RBWBRXQO.js.map +1 -0
- package/dist/{commands-L4AINARC.js → commands-4UXHBZFD.js} +127 -71
- package/dist/commands-4UXHBZFD.js.map +1 -0
- package/dist/{config-LUSJAK6B.js → config-GS7BTZJD.js} +2 -2
- package/dist/{configured-paths-SPQ5XWOM.js → configured-paths-CREUDQF3.js} +4 -4
- package/dist/contract-42CIWIWV.js +8 -0
- package/dist/{coordinator-SA75UB3C.js → coordinator-WOPGWDXK.js} +231 -144
- package/dist/coordinator-WOPGWDXK.js.map +1 -0
- package/dist/corpus-CAFYEMBO.js +15 -0
- package/dist/{doctor-DSC36R4U.js → doctor-AZP5D7QP.js} +21 -21
- package/dist/{feature-directories-VOHVV26X.js → feature-directories-6TLM7Y4H.js} +6 -6
- package/dist/{finalization-7SMLMVLV.js → finalization-AQT3SOP7.js} +4 -4
- package/dist/{gh-cli-ZMO7D2KE.js → gh-cli-XCQT5ST7.js} +2 -2
- package/dist/{github-rest-KGKS4V2H.js → github-rest-CXFJ2NFF.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{job-CDADLJDF.js → job-4POTQJZK.js} +20 -21
- package/dist/job-4POTQJZK.js.map +1 -0
- package/dist/{learning-sync-6BTHM6ZC.js → learning-sync-QVXBOP6J.js} +4 -4
- package/dist/{legacy-global-guidance-2JL4Y5EB.js → legacy-global-guidance-CPGUUNJI.js} +5 -5
- package/dist/{lint-gherkin-COSHVS6Z.js → lint-gherkin-LZ45NG4A.js} +7 -7
- package/dist/{operations-XVRQSLJG.js → operations-ODOA5CDG.js} +18 -16
- package/dist/{output-3LB2OEHN.js → output-QTZCFLAY.js} +2 -2
- package/dist/{packet-5WT5M3OS.js → packet-5G6RT2EW.js} +2 -2
- package/dist/presets/typescript/index.js +2 -2
- package/dist/{profile-UMKQ5JVO.js → profile-QGLZNLRU.js} +7 -7
- package/dist/{prompt-GWYDFMLO.js → prompt-UCTSCXSS.js} +2 -2
- package/dist/remove-UQPEXXNP.js +23 -0
- package/dist/{retro-IFSVU5KS.js → retro-ELKR6GHF.js} +8 -8
- package/dist/{retro-draft-spool-4S3TCHWL.js → retro-draft-spool-M75BTT32.js} +2 -2
- package/dist/{retro-extract-OSBFAXLU.js → retro-extract-RA6THOC4.js} +2 -2
- package/dist/{review-pr-6S2352CK.js → review-pr-MN6QESAJ.js} +2 -2
- package/dist/{review-pr-publication-CGHCRRUI.js → review-pr-publication-6AVOGWR2.js} +2 -2
- package/dist/{run-5JESKXUR.js → run-JFDGRAH2.js} +4 -4
- package/dist/{self-report-VPHBPPSJ.js → self-report-5APRPB5P.js} +2 -2
- package/dist/status-ATFG6Q3K.js +21 -0
- package/dist/{status-EX4L3ELO.js → status-HIQSP3F5.js} +21 -21
- package/dist/{sync-config-O7J7IDGE.js → sync-config-XPAJKBRO.js} +5 -5
- package/dist/{sync-tracker-U2GYOFUO.js → sync-tracker-3E5BWX73.js} +6 -6
- package/dist/{test-execution-6LIPMBIH.js → test-execution-VDECX7TO.js} +8 -7
- package/dist/{test-execution-6LIPMBIH.js.map → test-execution-VDECX7TO.js.map} +1 -1
- package/dist/{test-plan-VASHZXJ7.js → test-plan-2L7A7UZV.js} +7 -6
- package/dist/{test-plan-VASHZXJ7.js.map → test-plan-2L7A7UZV.js.map} +1 -1
- package/dist/{ticket-new-3YXDIWPC.js → ticket-new-HZKNZ5GO.js} +11 -11
- package/dist/ticket-new-HZKNZ5GO.js.map +1 -0
- package/dist/{ticket-sync-LLUBXZ2R.js → ticket-sync-4QJD3KOL.js} +5 -5
- package/dist/{tracker-map-HR4MBYG5.js → tracker-map-CFMFIEER.js} +2 -2
- package/dist/{tracker-sync-YDICZN66.js → tracker-sync-MOPJE6M7.js} +2 -2
- package/package.json +1 -1
- package/templates/SAFEWORD.md +2 -2
- package/templates/commands/lint.md +1 -1
- package/templates/scripts/closeout-cleanup.ts +127 -31
- package/templates/skills/bdd/SCENARIOS.md +3 -0
- package/templates/skills/bdd/TDD.md +11 -5
- package/templates/skills/closeout/SKILL.md +25 -17
- package/templates/skills/finish-review/SKILL.md +4 -3
- package/templates/skills/lint/SKILL.md +1 -1
- package/templates/skills/review-spec/SKILL.md +5 -1
- package/templates/skills/self-review/SKILL.md +2 -2
- package/templates/skills/ticket-system/SKILL.md +3 -0
- package/dist/chunk-6S7CTKGC.js +0 -128
- package/dist/chunk-6S7CTKGC.js.map +0 -1
- package/dist/chunk-AV5LTEME.js.map +0 -1
- package/dist/chunk-BSOUMHMQ.js.map +0 -1
- package/dist/chunk-J7UDLLTK.js.map +0 -1
- package/dist/chunk-KXHVFCDT.js.map +0 -1
- package/dist/chunk-NOTBZCKG.js.map +0 -1
- package/dist/chunk-QUDIOBHP.js.map +0 -1
- package/dist/chunk-S2T356DA.js.map +0 -1
- package/dist/chunk-SME5SU3O.js.map +0 -1
- package/dist/chunk-TJ627SKI.js.map +0 -1
- package/dist/chunk-USPVNZ63.js.map +0 -1
- package/dist/chunk-V63TSN3C.js.map +0 -1
- package/dist/chunk-WRUXO3WA.js +0 -48
- package/dist/chunk-WRUXO3WA.js.map +0 -1
- package/dist/chunk-WVDRXD67.js.map +0 -1
- package/dist/codex-bootstrap-MGY2D6HU.js +0 -178
- package/dist/codex-bootstrap-MGY2D6HU.js.map +0 -1
- package/dist/codex-hook-IPX3PDXX.js.map +0 -1
- package/dist/codify-J5ZLJ3HK.js.map +0 -1
- package/dist/commands-L4AINARC.js.map +0 -1
- package/dist/contract-KEZY6YC3.js +0 -8
- package/dist/coordinator-SA75UB3C.js.map +0 -1
- package/dist/corpus-PPOBTF4J.js +0 -15
- package/dist/job-CDADLJDF.js.map +0 -1
- package/dist/remove-E6HGNHOU.js +0 -22
- package/dist/status-7VIGNTUI.js +0 -21
- package/dist/ticket-new-3YXDIWPC.js.map +0 -1
- /package/dist/{architecture-N5ZC4CDU.js.map → architecture-TCSDEFKH.js.map} +0 -0
- /package/dist/{architecture-document-UBA5UT4J.js.map → architecture-document-L2ADWVMV.js.map} +0 -0
- /package/dist/{architecture-monorepo-E4JUYJGV.js.map → architecture-monorepo-NKOQZBEY.js.map} +0 -0
- /package/dist/{boundary-HLJILY7R.js.map → boundary-QP2H7C46.js.map} +0 -0
- /package/dist/{chunk-4CZS4YJV.js.map → chunk-3OKZ3X4O.js.map} +0 -0
- /package/dist/{chunk-GZ36UZJG.js.map → chunk-73TABQRT.js.map} +0 -0
- /package/dist/{chunk-4CKCRZDO.js.map → chunk-7ULZND56.js.map} +0 -0
- /package/dist/{chunk-34NCOTP5.js.map → chunk-7XELS325.js.map} +0 -0
- /package/dist/{chunk-BQTC6YGN.js.map → chunk-D5ENZR6N.js.map} +0 -0
- /package/dist/{chunk-ZRV7TOCX.js.map → chunk-G5AFQXXW.js.map} +0 -0
- /package/dist/{chunk-AXAGJLMP.js.map → chunk-G757F3EU.js.map} +0 -0
- /package/dist/{chunk-BAVDDAN3.js.map → chunk-KS2ESRQL.js.map} +0 -0
- /package/dist/{chunk-KFVEPQ7X.js.map → chunk-MVFHTUQ3.js.map} +0 -0
- /package/dist/{chunk-Q5KLE6ZF.js.map → chunk-OKPRQGFG.js.map} +0 -0
- /package/dist/{chunk-664YNAMR.js.map → chunk-RTFPKJJV.js.map} +0 -0
- /package/dist/{chunk-GKR44BCH.js.map → chunk-RVDXBHUG.js.map} +0 -0
- /package/dist/{chunk-A7Z27COA.js.map → chunk-VFWAZLEF.js.map} +0 -0
- /package/dist/{chunk-I4KBHRZW.js.map → chunk-VJUPD7JI.js.map} +0 -0
- /package/dist/{chunk-3EJNDIAO.js.map → chunk-VZ5B2SIO.js.map} +0 -0
- /package/dist/{cleanup-X556LNWM.js.map → cleanup-JNLZ7AXA.js.map} +0 -0
- /package/dist/{cleanup-command-RV2Q7G72.js.map → cleanup-command-MFAYNYRW.js.map} +0 -0
- /package/dist/{clients-HP23HRN2.js.map → clients-7AJBKEWF.js.map} +0 -0
- /package/dist/{config-LUSJAK6B.js.map → config-GS7BTZJD.js.map} +0 -0
- /package/dist/{configured-paths-SPQ5XWOM.js.map → configured-paths-CREUDQF3.js.map} +0 -0
- /package/dist/{contract-KEZY6YC3.js.map → contract-42CIWIWV.js.map} +0 -0
- /package/dist/{corpus-PPOBTF4J.js.map → corpus-CAFYEMBO.js.map} +0 -0
- /package/dist/{doctor-DSC36R4U.js.map → doctor-AZP5D7QP.js.map} +0 -0
- /package/dist/{feature-directories-VOHVV26X.js.map → feature-directories-6TLM7Y4H.js.map} +0 -0
- /package/dist/{finalization-7SMLMVLV.js.map → finalization-AQT3SOP7.js.map} +0 -0
- /package/dist/{gh-cli-ZMO7D2KE.js.map → gh-cli-XCQT5ST7.js.map} +0 -0
- /package/dist/{github-rest-KGKS4V2H.js.map → github-rest-CXFJ2NFF.js.map} +0 -0
- /package/dist/{learning-sync-6BTHM6ZC.js.map → learning-sync-QVXBOP6J.js.map} +0 -0
- /package/dist/{legacy-global-guidance-2JL4Y5EB.js.map → legacy-global-guidance-CPGUUNJI.js.map} +0 -0
- /package/dist/{lint-gherkin-COSHVS6Z.js.map → lint-gherkin-LZ45NG4A.js.map} +0 -0
- /package/dist/{operations-XVRQSLJG.js.map → operations-ODOA5CDG.js.map} +0 -0
- /package/dist/{output-3LB2OEHN.js.map → output-QTZCFLAY.js.map} +0 -0
- /package/dist/{packet-5WT5M3OS.js.map → packet-5G6RT2EW.js.map} +0 -0
- /package/dist/{profile-UMKQ5JVO.js.map → profile-QGLZNLRU.js.map} +0 -0
- /package/dist/{prompt-GWYDFMLO.js.map → prompt-UCTSCXSS.js.map} +0 -0
- /package/dist/{remove-E6HGNHOU.js.map → remove-UQPEXXNP.js.map} +0 -0
- /package/dist/{retro-IFSVU5KS.js.map → retro-ELKR6GHF.js.map} +0 -0
- /package/dist/{retro-draft-spool-4S3TCHWL.js.map → retro-draft-spool-M75BTT32.js.map} +0 -0
- /package/dist/{retro-extract-OSBFAXLU.js.map → retro-extract-RA6THOC4.js.map} +0 -0
- /package/dist/{review-pr-6S2352CK.js.map → review-pr-MN6QESAJ.js.map} +0 -0
- /package/dist/{review-pr-publication-CGHCRRUI.js.map → review-pr-publication-6AVOGWR2.js.map} +0 -0
- /package/dist/{run-5JESKXUR.js.map → run-JFDGRAH2.js.map} +0 -0
- /package/dist/{self-report-VPHBPPSJ.js.map → self-report-5APRPB5P.js.map} +0 -0
- /package/dist/{status-7VIGNTUI.js.map → status-ATFG6Q3K.js.map} +0 -0
- /package/dist/{status-EX4L3ELO.js.map → status-HIQSP3F5.js.map} +0 -0
- /package/dist/{sync-config-O7J7IDGE.js.map → sync-config-XPAJKBRO.js.map} +0 -0
- /package/dist/{sync-tracker-U2GYOFUO.js.map → sync-tracker-3E5BWX73.js.map} +0 -0
- /package/dist/{ticket-sync-LLUBXZ2R.js.map → ticket-sync-4QJD3KOL.js.map} +0 -0
- /package/dist/{tracker-map-HR4MBYG5.js.map → tracker-map-CFMFIEER.js.map} +0 -0
- /package/dist/{tracker-sync-YDICZN66.js.map → tracker-sync-MOPJE6M7.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reconcile.ts","../src/packs/rust/setup.ts","../src/packs/config.ts","../src/packs/golang/setup.ts","../src/packs/golang/index.ts","../src/packs/python/index.ts","../src/packs/rust/index.ts","../src/packs/sql/index.ts","../src/packs/typescript/index.ts","../src/packs/typescript/setup.ts","../src/packs/registry.ts","../src/utils/context.ts","../src/utils/git.ts","../src/utils/hook-manager.ts","../src/utils/project-detector.ts","../src/utils/cucumber-template-revisions.ts"],"sourcesContent":["/**\n * Reconciliation Engine\n *\n * Computes and executes plans based on SAFEWORD_SCHEMA and project state.\n * This is the single source of truth for all file/dir/config operations.\n */\n\nimport { lstatSync, readlinkSync, unlinkSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport type {\n FileDefinition,\n JsonMergeDefinition,\n ManagedFileDefinition,\n ProjectContext,\n SafewordSchema,\n TextPatchDefinition,\n} from './schema.js';\nimport {\n NAMESPACE_ROOT_LEGACY,\n readConfiguredPath,\n resolveNamespaceRoot,\n} from './utils/configured-paths.js';\nimport {\n ensureDirectory,\n exists,\n getTemplatesDirectory,\n makeScriptsExecutable,\n readFile,\n readFileSafe,\n readJson,\n remove,\n removeIfEmpty,\n writeFile,\n writeJson,\n} from './utils/fs.js';\nimport type { ProjectType } from './utils/project-detector.js';\nimport { getWorkspacePackageNames } from './utils/workspaces.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst HUSKY_DIR = '.husky';\n\n/**\n * Directories containing executable scripts that need chmod +x.\n * Used by both install and upgrade plans.\n */\nconst CHMOD_PATHS = ['.safeword/hooks', '.safeword/hooks/cursor', '.safeword/scripts'];\n\n/**\n * Prettier-related packages that should be skipped for projects with existing formatter.\n */\nconst PRETTIER_PACKAGES = new Set([\n 'prettier',\n 'prettier-plugin-astro',\n 'prettier-plugin-tailwindcss',\n 'prettier-plugin-sh',\n]);\n\n// Conditional-package keys whose condition is the ABSENCE of existing tooling,\n// rather than a truthy ProjectType field: \"standard\" = no existing formatter.\nconst INVERTED_PACKAGE_CONDITIONS: Record<string, (projectType: ProjectType) => boolean> = {\n standard: projectType => !projectType.existingFormatter,\n};\n\n/**\n * Get conditional packages based on project type.\n * Handles inverted keys (standard) and prettier filtering for existing formatters.\n */\nfunction getConditionalPackages(\n conditionalPackages: Record<string, string[]>,\n projectType: ProjectType,\n): string[] {\n const packages: string[] = [];\n\n for (const [key, dependencies] of Object.entries(conditionalPackages)) {\n const inverted = INVERTED_PACKAGE_CONDITIONS[key];\n if (inverted) {\n if (inverted(projectType)) packages.push(...dependencies);\n continue;\n }\n\n // Check if this condition is met\n const conditionMet = projectType[key as keyof ProjectType];\n if (conditionMet) {\n // For projects with existing formatter, skip prettier-related packages\n if (projectType.existingFormatter) {\n packages.push(...dependencies.filter(pkg => !PRETTIER_PACKAGES.has(pkg)));\n } else {\n packages.push(...dependencies);\n }\n }\n }\n\n return packages;\n}\n\n/** Check if path should be skipped in non-git repos (husky files) */\nfunction shouldSkipForNonGit(path: string, isGitRepo: boolean): boolean {\n return path.startsWith(HUSKY_DIR) && !isGitRepo;\n}\n\n/** Plan mkdir actions for directories that don't exist */\nfunction planMissingDirectories(\n directories: string[],\n cwd: string,\n isGitRepo: boolean,\n): { actions: Action[]; created: string[] } {\n const actions: Action[] = [];\n const created: string[] = [];\n for (const dir of directories) {\n if (shouldSkipForNonGit(dir, isGitRepo)) continue;\n if (!exists(nodePath.join(cwd, dir))) {\n actions.push({ type: 'mkdir', path: dir });\n created.push(dir);\n }\n }\n return { actions, created };\n}\n\n/**\n * Plan text-patch actions for all eligible targets. The executor\n * (`executeTextPatch`) decides whether to prepend, heal a legacy `---#`\n * artifact, or no-op based on the file's current contents — so the planner\n * stays uniform across modes for unguarded patches.\n * Keeping the marker check in the executor (not here) ensures `safeword\n * upgrade` reaches the heal path on pre-fix installs, which is what commit\n * a304af8 promised.\n */\nfunction asPatchList(entry: TextPatchDefinition | TextPatchDefinition[]): TextPatchDefinition[] {\n return Array.isArray(entry) ? entry : [entry];\n}\n\nfunction planTextPatches(\n patches: Record<string, TextPatchDefinition | TextPatchDefinition[]>,\n ctx: ProjectContext,\n): Action[] {\n const actions: Action[] = [];\n for (const [filePath, entry] of Object.entries(patches)) {\n if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;\n // Apply in list order so later patches land beneath earlier ones.\n actions.push(...planTextPatchesForFile(filePath, entry, ctx));\n }\n return actions;\n}\n\nfunction planTextPatchesForFile(\n filePath: string,\n entry: TextPatchDefinition | TextPatchDefinition[],\n ctx: ProjectContext,\n): Action[] {\n const actions: Action[] = [];\n for (const definition of asPatchList(entry)) {\n // Application-only gate (ZJMZ50): unpatch stays ungated in planTextUnpatches.\n if (definition.when !== undefined && !definition.when(ctx)) continue;\n if (!passesTextPatchContentGuard(ctx.cwd, filePath, definition)) continue;\n actions.push({\n type: 'text-patch',\n path: filePath,\n definition: resolveTextPatch(definition, ctx),\n });\n }\n return actions;\n}\n\nfunction passesTextPatchContentGuard(\n cwd: string,\n filePath: string,\n definition: TextPatchDefinition,\n): boolean {\n if (!definition.applyWhenContentIncludes) return true;\n\n const content = readFileSafe(nodePath.join(cwd, filePath));\n return (\n content !== undefined &&\n definition.applyWhenContentIncludes.every(required => content.includes(required))\n );\n}\n\nfunction planTextUnpatches(\n patches: Record<string, TextPatchDefinition | TextPatchDefinition[]>,\n ctx: ProjectContext,\n): Action[] {\n const hasLegacyPreamble = (content: string, definition: TextPatchDefinition): boolean => {\n const firstLine = content.split('\\n', 1)[0] ?? '';\n return containsTextPatchContent(content, definition) || firstLine.includes(definition.marker);\n };\n\n const actions: Action[] = [];\n for (const [filePath, entry] of Object.entries(patches)) {\n if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;\n\n const fullPath = nodePath.join(ctx.cwd, filePath);\n if (!exists(fullPath)) continue;\n\n const content = readFileSafe(fullPath) ?? '';\n // Unpatch in reverse list order so the patch that owns file removal\n // (removeFileIfContentEquals) runs last, after siblings strip their blocks.\n for (const definition of asPatchList(entry).toReversed()) {\n if (hasLegacyPreamble(content, definition)) {\n actions.push({\n type: 'text-unpatch',\n path: filePath,\n definition: resolveTextPatch(definition, ctx),\n });\n }\n }\n }\n return actions;\n}\n\nfunction stripLeadingSafewordSeparator(value: string): string {\n if (value.startsWith('\\n---')) {\n return value.slice('\\n---'.length).replace(/^\\n+/, '');\n }\n if (value.startsWith('---')) {\n return value.slice('---'.length).replace(/^\\n+/, '');\n }\n return value;\n}\n\n/**\n * Generic file write planner with configurable skip condition.\n */\nfunction planFileWrites(\n files: Record<string, FileDefinition>,\n ctx: ProjectContext,\n shouldSkip: (filePath: string, ctx: ProjectContext) => boolean,\n): { actions: Action[]; created: string[] } {\n const actions: Action[] = [];\n const created: string[] = [];\n for (const [filePath, definition] of Object.entries(files)) {\n if (shouldSkip(filePath, ctx)) continue;\n const content = resolveFileContent(definition, ctx);\n // Skip files where generator returned undefined (e.g., non-JS projects)\n if (content === undefined) continue;\n actions.push({ type: 'write', path: filePath, content });\n created.push(filePath);\n }\n return { actions, created };\n}\n\n/** Owned files: skip husky files in non-git repos */\nfunction planOwnedFileWrites(\n files: Record<string, FileDefinition>,\n ctx: ProjectContext,\n): { actions: Action[]; created: string[] } {\n return planFileWrites(files, ctx, (filePath, c) => shouldSkipForNonGit(filePath, c.isGitRepo));\n}\n\n/**\n * Managed files: skip if file already exists OR if the entry has a\n * `configKey` whose `paths.<configKey>` override is set in\n * `.safeword/config.json` — see ticket K7N2QM.\n */\nfunction planManagedFileWrites(\n files: Record<string, ManagedFileDefinition>,\n ctx: ProjectContext,\n): { actions: Action[]; created: string[] } {\n return planFileWrites(files, ctx, (filePath, c) => {\n const definition = files[filePath];\n if (definition && isConfigOverridden(definition, c.cwd)) return true;\n return exists(nodePath.join(c.cwd, filePath));\n });\n}\n\n/** Plan rmdir actions for directories that exist */\nfunction planExistingDirectoriesRemoval(\n directories: string[],\n cwd: string,\n): { actions: Action[]; removed: string[] } {\n const actions: Action[] = [];\n const removed: string[] = [];\n for (const dir of directories) {\n if (!exists(nodePath.join(cwd, dir))) {\n continue;\n }\n\n actions.push({ type: 'rmdir', path: dir });\n removed.push(dir);\n }\n return { actions, removed };\n}\n\nfunction matchesUnmodifiedScaffold(\n definition: ManagedFileDefinition,\n ctx: ProjectContext,\n installed: string,\n): boolean {\n const expected = definition.removeIfUnmodified?.(ctx);\n if (expected === undefined) return false;\n const normalize = definition.normalizeForUnmodifiedComparison;\n return normalize === undefined\n ? installed === expected\n : normalize(installed) === normalize(expected);\n}\n\n/** Plan rm actions for files that exist */\n/**\n * Conditional managed-file removal on DEFAULT reset (ticket V4MATC): entries\n * opting in via `removeIfUnmodified` are removed iff the on-disk content\n * byte-equals the expected scaffold — a user-extended or user-authored file\n * never matches and survives.\n */\nfunction planConditionalManagedRemoval(\n managedFiles: Record<string, ManagedFileDefinition>,\n ctx: ProjectContext,\n): { actions: Action[]; removed: string[] } {\n const actions: Action[] = [];\n const removed: string[] = [];\n for (const [filePath, definition] of Object.entries(managedFiles)) {\n if (definition.removeIfUnmodified === undefined) continue;\n if (isConfigOverridden(definition, ctx.cwd)) continue;\n const fullPath = nodePath.join(ctx.cwd, filePath);\n if (!exists(fullPath)) continue;\n const installed = readFileSafe(fullPath);\n if (installed !== undefined && matchesUnmodifiedScaffold(definition, ctx, installed)) {\n actions.push({ type: 'rm', path: filePath });\n removed.push(filePath);\n }\n }\n return { actions, removed };\n}\n\n/**\n * Conditional feature opt-out during upgrade: when a managed file's generator\n * now omits it, remove only the exact scaffold Safeword would have installed.\n * A customized file is project-owned and must survive the configuration change.\n */\nfunction planOmittedManagedRemoval(\n managedFiles: Record<string, ManagedFileDefinition>,\n ctx: ProjectContext,\n): { actions: Action[]; removed: string[] } {\n const actions: Action[] = [];\n const removed: string[] = [];\n for (const [filePath, definition] of Object.entries(managedFiles)) {\n if (!definition.removeWhenGeneratorOmitted) continue;\n if (definition.removeIfUnmodified === undefined) continue;\n if (isConfigOverridden(definition, ctx.cwd)) continue;\n if (resolveFileContent(definition, ctx) !== undefined) continue;\n\n const fullPath = nodePath.join(ctx.cwd, filePath);\n if (!exists(fullPath)) continue;\n const installed = readFileSafe(fullPath);\n if (installed !== undefined && matchesUnmodifiedScaffold(definition, ctx, installed)) {\n actions.push({ type: 'rm', path: filePath });\n removed.push(filePath);\n }\n }\n return { actions, removed };\n}\n\nfunction planExistingFilesRemoval(\n files: string[],\n cwd: string,\n): { actions: Action[]; removed: string[] } {\n const actions: Action[] = [];\n const removed: string[] = [];\n for (const filePath of files) {\n const fullPath = nodePath.join(cwd, filePath);\n const stat = lstatIfExists(fullPath);\n if (stat === undefined) continue;\n if (!isRemovableFile(stat)) continue;\n\n actions.push({ type: 'rm', path: filePath });\n removed.push(filePath);\n }\n return { actions, removed };\n}\n\nfunction lstatIfExists(path: string): ReturnType<typeof lstatSync> | undefined {\n try {\n return lstatSync(path);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ENOTDIR') return undefined;\n throw error;\n }\n}\n\n/** A schema file entry may unlink a file or symlink, but must preserve directories. */\nfunction isRemovableFile(stat: NonNullable<ReturnType<typeof lstatSync>>): boolean {\n return stat.isFile() || stat.isSymbolicLink();\n}\n\n/** Check if a .claude path needs parent dir cleanup */\nfunction getClaudeParentDirectoryForCleanup(filePath: string): string | undefined {\n if (!filePath.startsWith('.claude/')) return undefined;\n const parentDirectory = filePath.slice(0, Math.max(0, filePath.lastIndexOf('/')));\n if (\n !parentDirectory ||\n parentDirectory === '.claude' ||\n parentDirectory === '.claude/skills' ||\n parentDirectory === '.claude/commands'\n ) {\n return undefined;\n }\n return parentDirectory;\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype ReconcileMode = 'install' | 'upgrade' | 'uninstall' | 'uninstall-full';\n\nexport type Action =\n | { type: 'mkdir'; path: string }\n | { type: 'rmdir'; path: string }\n | { type: 'write'; path: string; content: string }\n | { type: 'rm'; path: string }\n | { type: 'chmod'; paths: string[] }\n | { type: 'json-merge'; path: string; definition: JsonMergeDefinition }\n | { type: 'json-unmerge'; path: string; definition: JsonMergeDefinition }\n | { type: 'text-patch'; path: string; definition: ResolvedTextPatch }\n | { type: 'text-unpatch'; path: string; definition: ResolvedTextPatch };\n\n// A TextPatchDefinition whose ctx-factory `content` has been resolved to a string\n// at plan time, so executors never see a function (#293).\ntype ResolvedTextPatch = Omit<TextPatchDefinition, 'content'> & { content: string };\n\nfunction resolveTextPatch(definition: TextPatchDefinition, ctx: ProjectContext): ResolvedTextPatch {\n return {\n ...definition,\n content:\n typeof definition.content === 'function' ? definition.content(ctx) : definition.content,\n };\n}\n\nexport interface ReconcileResult {\n actions: Action[];\n applied: boolean;\n created: string[];\n updated: string[];\n removed: string[];\n packagesToInstall: string[];\n packagesToRemove: string[];\n // Non-fatal issues surfaced during execution (e.g. a jsonMerge target that\n // exists but doesn't parse, so the merge was skipped rather than silently lost).\n // Empty on a dry run, which never executes merges.\n warnings: string[];\n}\n\ninterface ReconcileOptions {\n dryRun?: boolean;\n}\n\n// ============================================================================\n// Main reconcile function\n// ============================================================================\n\n/**\n * Realize the schema's namespace paths at the resolved root (ticket N9S5XG).\n *\n * The schema manifest stays static with legacy-prefixed namespace entries\n * (`.safeword-project/...`); at planning time those entries are mapped onto\n * the resolved root — `.project/` for fresh repos, an adopted existing\n * `.project/`, a configured `paths.projectRoot`, or the legacy root itself\n * (identity translation) where only it exists. One seam covers install,\n * upgrade, diff (dry-run upgrade), and uninstall alike.\n */\nfunction withResolvedNamespaceRoot(schema: SafewordSchema, ctx: ProjectContext): SafewordSchema {\n const root = ctx.namespaceRoot ?? resolveNamespaceRoot(ctx.cwd);\n // Empty relative label means the namespace root IS the repo root\n // (paths.projectRoot: '.') — translate to bare subpaths, not legacy.\n const label = nodePath.relative(ctx.cwd, root) || '.';\n if (label === NAMESPACE_ROOT_LEGACY) return schema;\n\n const translate = (path: string): string => {\n if (path !== NAMESPACE_ROOT_LEGACY && !path.startsWith(`${NAMESPACE_ROOT_LEGACY}/`)) {\n return path;\n }\n const subpath = path.slice(NAMESPACE_ROOT_LEGACY.length).replace(/^\\//, '');\n return label === '.' ? subpath || '.' : nodePath.join(label, subpath);\n };\n\n return {\n ...schema,\n preservedDirs: schema.preservedDirs.map(path => translate(path)),\n managedFiles: Object.fromEntries(\n Object.entries(schema.managedFiles)\n .map(([path, definition]) => [translate(path), definition] as const)\n // A per-root `.gitignore` (issue #272) translated onto the repo root\n // (paths.projectRoot: '.') would BE the user's own root `.gitignore` —\n // owned by the textPatch, never created by us (create-if-missing skips\n // an existing file). Drop it there so a full uninstall can't delete a\n // file we didn't write.\n .filter(([translatedPath]) => translatedPath !== '.gitignore'),\n ),\n };\n}\n\nexport async function reconcile(\n schema: SafewordSchema,\n mode: ReconcileMode,\n ctx: ProjectContext,\n options?: ReconcileOptions,\n): Promise<ReconcileResult> {\n // Public API contract is Promise<ReconcileResult>; some plan executors are\n // sync today, but the signature reserves room for async I/O without\n // breaking callers. Token await keeps the contract honest.\n await Promise.resolve();\n const isDryRun = options?.dryRun ?? false;\n\n const plan = computePlan(withResolvedNamespaceRoot(schema, ctx), mode, ctx);\n\n if (isDryRun) {\n return {\n actions: plan.actions,\n applied: false,\n created: plan.wouldCreate,\n updated: plan.wouldUpdate,\n removed: plan.wouldRemove,\n packagesToInstall: plan.packagesToInstall,\n packagesToRemove: plan.packagesToRemove,\n warnings: [],\n };\n }\n\n const result = executePlan(plan, ctx);\n\n return {\n actions: plan.actions,\n applied: true,\n created: result.created,\n updated: result.updated,\n removed: result.removed,\n packagesToInstall: plan.packagesToInstall,\n packagesToRemove: plan.packagesToRemove,\n warnings: result.warnings,\n };\n}\n\n// ============================================================================\n// Plan computation\n// ============================================================================\n\ninterface ReconcilePlan {\n actions: Action[];\n wouldCreate: string[];\n wouldUpdate: string[];\n wouldRemove: string[];\n packagesToInstall: string[];\n packagesToRemove: string[];\n}\n\nfunction computePlan(\n schema: SafewordSchema,\n mode: ReconcileMode,\n ctx: ProjectContext,\n): ReconcilePlan {\n switch (mode) {\n case 'install': {\n return computeInstallPlan(schema, ctx);\n }\n case 'upgrade': {\n return computeUpgradePlan(schema, ctx);\n }\n case 'uninstall': {\n return computeUninstallPlan(schema, ctx, false);\n }\n case 'uninstall-full': {\n return computeUninstallPlan(schema, ctx, true);\n }\n default: {\n // Exhaustive check - TypeScript ensures all cases are handled\n const _exhaustiveCheck: never = mode;\n return _exhaustiveCheck;\n }\n }\n}\n\nfunction computeInstallPlan(schema: SafewordSchema, ctx: ProjectContext): ReconcilePlan {\n const actions: Action[] = [];\n const wouldCreate: string[] = [];\n const wouldUpdate: string[] = [];\n const wouldRemove: string[] = [];\n\n // 1. Create all directories\n const allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];\n const directories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);\n actions.push(...directories.actions);\n wouldCreate.push(...directories.created);\n\n // 2. Write owned files\n const owned = planOwnedFileWrites(schema.ownedFiles, ctx);\n actions.push(...owned.actions);\n wouldCreate.push(...owned.created);\n\n // 3. Write managed files (only if missing)\n const managed = planManagedFileWrites(schema.managedFiles, ctx);\n actions.push(...managed.actions);\n wouldCreate.push(...managed.created);\n\n // 4. chmod hook/lib/scripts directories\n const chmodPaths = [...CHMOD_PATHS];\n if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);\n actions.push({ type: 'chmod', paths: chmodPaths });\n\n // 5. JSON merges\n for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-merge', path: filePath, definition });\n }\n\n // 6. Text patches — unguarded patches create absent target files; guarded\n // patches only run when the current file proves it is safe to touch.\n actions.push(...planTextUnpatches(schema.legacyTextPatches, ctx));\n const textPatchActions = planTextPatches(schema.textPatches, ctx);\n actions.push(...textPatchActions);\n const dynamicChanges = planDynamicFileChanges(actions, ctx);\n wouldCreate.push(...dynamicChanges.created);\n wouldUpdate.push(...dynamicChanges.updated);\n wouldRemove.push(...dynamicChanges.removed);\n\n // 7. Compute packages to install\n const packagesToInstall = computePackagesToInstall(\n schema,\n ctx.projectType,\n ctx.developmentDeps,\n ctx.cwd,\n );\n\n return {\n actions,\n wouldCreate: [...new Set(wouldCreate)],\n wouldUpdate: [...new Set(wouldUpdate)],\n wouldRemove: [...new Set(wouldRemove)],\n packagesToInstall,\n packagesToRemove: [],\n };\n}\n\ninterface FileActionResult {\n actions: Action[];\n created: string[];\n updated: string[];\n}\n\n/**\n * Plan actions for owned files (always update if content changed).\n */\nfunction planOwnedFilesActions(\n ownedFiles: Record<string, FileDefinition>,\n ctx: ProjectContext,\n): FileActionResult {\n const actions: Action[] = [];\n const created: string[] = [];\n const updated: string[] = [];\n\n for (const [filePath, definition] of Object.entries(ownedFiles)) {\n if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;\n\n const fullPath = nodePath.join(ctx.cwd, filePath);\n const newContent = resolveFileContent(definition, ctx);\n\n if (newContent === undefined) continue;\n if (!fileNeedsUpdate(fullPath, newContent)) continue;\n\n actions.push({ type: 'write', path: filePath, content: newContent });\n if (exists(fullPath)) {\n updated.push(filePath);\n } else {\n created.push(filePath);\n }\n }\n\n return { actions, created, updated };\n}\n\n/**\n * True when the managed-file entry has a `configKey` AND a corresponding\n * `paths.<configKey>` override is set in `.safeword/config.json`. Used to\n * suppress install scaffolding when the user has redirected the read target\n * (ticket K7N2QM). Authored knowledge is always preserved during uninstall.\n */\nfunction isConfigOverridden(definition: ManagedFileDefinition, cwd: string): boolean {\n if (!definition.configKey) return false;\n return readConfiguredPath(cwd, definition.configKey) !== undefined;\n}\n\n/**\n * Plan actions for managed files (only create if missing).\n *\n * Entries with a `configKey` whose `paths.<configKey>` override is set in\n * `.safeword/config.json` are suppressed — the user has redirected\n * safeword to read from elsewhere, so the default location is no longer\n * safeword's concern (ticket K7N2QM).\n */\nfunction planManagedFilesActions(\n managedFiles: Record<string, ManagedFileDefinition>,\n ctx: ProjectContext,\n): FileActionResult {\n const actions: Action[] = [];\n const created: string[] = [];\n\n for (const [filePath, definition] of Object.entries(managedFiles)) {\n if (isConfigOverridden(definition, ctx.cwd)) continue;\n\n const fullPath = nodePath.join(ctx.cwd, filePath);\n const newContent = resolveFileContent(definition, ctx);\n\n if (newContent === undefined) continue;\n if (exists(fullPath)) continue; // Don't update during upgrade\n\n actions.push({ type: 'write', path: filePath, content: newContent });\n created.push(filePath);\n }\n\n return { actions, created, updated: [] };\n}\n\nfunction computeUpgradePlan(schema: SafewordSchema, ctx: ProjectContext): ReconcilePlan {\n const actions: Action[] = [];\n const wouldCreate: string[] = [];\n const wouldUpdate: string[] = [];\n const wouldRemove: string[] = [];\n\n // 1. Ensure directories exist (skip .husky if not a git repo)\n const allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];\n const missingDirectories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);\n actions.push(...missingDirectories.actions);\n wouldCreate.push(...missingDirectories.created);\n\n // 2. Update owned files if content changed\n const ownedFilesResult = planOwnedFilesActions(schema.ownedFiles, ctx);\n actions.push(...ownedFilesResult.actions);\n wouldCreate.push(...ownedFilesResult.created);\n wouldUpdate.push(...ownedFilesResult.updated);\n\n // 3. Create missing managed files (don't update existing)\n const managedFilesResult = planManagedFilesActions(schema.managedFiles, ctx);\n actions.push(...managedFilesResult.actions);\n wouldCreate.push(...managedFilesResult.created);\n\n // 3b. Remove exact conditional scaffolds when their feature is disabled.\n const omittedManagedFiles = planOmittedManagedRemoval(schema.managedFiles, ctx);\n actions.push(...omittedManagedFiles.actions);\n wouldRemove.push(...omittedManagedFiles.removed);\n\n // 4. Remove deprecated files (renamed or removed in newer versions)\n const deprecatedFiles = planExistingFilesRemoval(schema.deprecatedFiles, ctx.cwd);\n actions.push(...deprecatedFiles.actions);\n wouldRemove.push(...deprecatedFiles.removed);\n\n // 4b. Remove deprecated directories (no longer managed by safeword)\n const deprecatedDirectories = planExistingDirectoriesRemoval(schema.deprecatedDirs, ctx.cwd);\n actions.push(...deprecatedDirectories.actions);\n wouldRemove.push(...deprecatedDirectories.removed);\n\n // 5. chmod\n actions.push({ type: 'chmod', paths: CHMOD_PATHS });\n\n // 6. JSON merges (always apply to ensure keys are present)\n for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-merge', path: filePath, definition });\n }\n\n // 7. Text patches\n actions.push(\n ...planTextUnpatches(schema.legacyTextPatches, ctx),\n ...planTextPatches(schema.textPatches, ctx),\n );\n const dynamicChanges = planDynamicFileChanges(actions, ctx);\n wouldCreate.push(...dynamicChanges.created);\n wouldUpdate.push(...dynamicChanges.updated);\n wouldRemove.push(...dynamicChanges.removed);\n\n // 8. Compute packages to install\n const packagesToInstall = computePackagesToInstall(\n schema,\n ctx.projectType,\n ctx.developmentDeps,\n ctx.cwd,\n );\n\n // 9. Compute deprecated packages to remove (only those actually installed)\n const packagesToRemove = schema.deprecatedPackages.filter(pkg =>\n Object.hasOwn(ctx.developmentDeps, pkg),\n );\n\n return {\n actions,\n wouldCreate: [...new Set(wouldCreate)],\n wouldUpdate: [...new Set(wouldUpdate)],\n wouldRemove: [...new Set(wouldRemove)],\n packagesToInstall,\n packagesToRemove,\n };\n}\n\nfunction computeUninstallPlan(\n schema: SafewordSchema,\n ctx: ProjectContext,\n full: boolean,\n): ReconcilePlan {\n const actions: Action[] = [];\n const wouldRemove: string[] = [];\n\n // 1. Remove all owned files and track parent dirs for cleanup.\n // cucumber.mjs is only safeword's when the lane is safeword's to scaffold —\n // in a suppressed-lane repo the root cucumber.mjs is the HOST's own config\n // and must survive uninstall (ticket 56JCFZ, TB1.AC3).\n const removableOwnedPaths = Object.keys(schema.ownedFiles).filter(\n filePath => filePath !== 'cucumber.mjs' || ctx.projectType.scaffoldBddLane,\n );\n const ownedFiles = planExistingFilesRemoval(removableOwnedPaths, ctx.cwd);\n actions.push(...ownedFiles.actions);\n wouldRemove.push(...ownedFiles.removed);\n\n // Collect parent dirs that need cleanup (for .claude/* skill dirs)\n const directoriesToCleanup = new Set<string>();\n for (const filePath of ownedFiles.removed) {\n const parentDirectory = getClaudeParentDirectoryForCleanup(filePath);\n if (parentDirectory) directoriesToCleanup.add(parentDirectory);\n }\n const cleanupDirectories = planExistingDirectoriesRemoval([...directoriesToCleanup], ctx.cwd);\n actions.push(...cleanupDirectories.actions);\n wouldRemove.push(...cleanupDirectories.removed);\n\n // 2. JSON unmerges\n for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-unmerge', path: filePath, definition });\n }\n\n // 3. Text unpatches\n actions.push(\n ...planTextUnpatches(schema.textPatches, ctx),\n ...planTextUnpatches(schema.legacyTextPatches, ctx),\n );\n const dynamicChanges = planDynamicFileChanges(actions, ctx);\n wouldRemove.push(...dynamicChanges.removed);\n\n // 4. Remove preserved directories first (reverse order, only if empty)\n const preserved = planExistingDirectoriesRemoval(schema.preservedDirs.toReversed(), ctx.cwd);\n actions.push(...preserved.actions);\n wouldRemove.push(...preserved.removed);\n\n // 5. Remove owned directories (reverse order ensures children before parents)\n const owned = planExistingDirectoriesRemoval(schema.ownedDirs.toReversed(), ctx.cwd);\n actions.push(...owned.actions);\n wouldRemove.push(...owned.removed);\n\n // 6. Full uninstall: remove managed tooling, but preserve every project-owned\n // knowledge entry identified by configKey. Those files become authored\n // project context after scaffolding and must survive Safeword removal,\n // regardless of whether their configured path is default or overridden.\n if (full) {\n const removable = Object.entries(schema.managedFiles)\n .filter(([, definition]) => definition.configKey === undefined)\n .map(([filePath]) => filePath);\n const managed = planExistingFilesRemoval(removable, ctx.cwd);\n actions.push(...managed.actions);\n wouldRemove.push(...managed.removed);\n } else {\n // 6b. Default uninstall: conditional managed-file removal (ticket V4MATC).\n const conditional = planConditionalManagedRemoval(schema.managedFiles, ctx);\n actions.push(...conditional.actions);\n wouldRemove.push(...conditional.removed);\n }\n\n // 7. Compute packages to remove (full only)\n const packagesToRemove = full\n ? computePackagesToRemove(schema, ctx.projectType, ctx.developmentDeps)\n : [];\n\n return {\n actions,\n wouldCreate: dynamicChanges.created,\n wouldUpdate: dynamicChanges.updated,\n wouldRemove: [...new Set(wouldRemove)],\n packagesToInstall: [],\n packagesToRemove,\n };\n}\n\ninterface DynamicFileChanges {\n readonly created: string[];\n readonly updated: string[];\n readonly removed: string[];\n}\n\n/**\n * Predict content-level action effects without executing them. Reconciliation\n * deliberately keeps executable actions even when their merge or patch is\n * already satisfied; public Plans must describe changes, not executor work.\n */\nfunction planDynamicFileChanges(\n actions: readonly Action[],\n ctx: ProjectContext,\n): DynamicFileChanges {\n const created = new Set<string>();\n const updated = new Set<string>();\n const removed = new Set<string>();\n\n for (const action of actions) {\n planDynamicActionChange(action, ctx, { created, updated, removed });\n }\n\n return {\n created: [...created],\n updated: [...updated],\n removed: [...removed],\n };\n}\n\nfunction planDynamicActionChange(\n action: Action,\n ctx: ProjectContext,\n changes: {\n readonly created: Set<string>;\n readonly updated: Set<string>;\n readonly removed: Set<string>;\n },\n): void {\n switch (action.type) {\n case 'json-merge': {\n planJsonMergeChange(action, ctx, changes.created, changes.updated);\n return;\n }\n case 'json-unmerge': {\n planJsonUnmergeChange(action, ctx, changes.updated, changes.removed);\n return;\n }\n case 'text-patch': {\n planTextPatchChange(action, ctx, changes.created, changes.updated);\n return;\n }\n case 'text-unpatch': {\n planTextUnpatchChange(action, ctx, changes.updated, changes.removed);\n return;\n }\n case 'chmod':\n case 'mkdir':\n case 'rm':\n case 'rmdir':\n case 'write': {\n return;\n }\n }\n}\n\nfunction planJsonMergeChange(\n action: Extract<Action, { type: 'json-merge' }>,\n ctx: ProjectContext,\n created: Set<string>,\n updated: Set<string>,\n): void {\n const fullPath = nodePath.join(ctx.cwd, action.path);\n const existing = readJson(fullPath) as Record<string, unknown> | undefined;\n if (existing === undefined && (exists(fullPath) || action.definition.skipIfMissing)) return;\n const merged = action.definition.merge(existing ?? {}, ctx);\n if (JSON.stringify(existing ?? {}) === JSON.stringify(merged)) return;\n if (existing === undefined) created.add(action.path);\n else updated.add(action.path);\n}\n\nfunction planJsonUnmergeChange(\n action: Extract<Action, { type: 'json-unmerge' }>,\n ctx: ProjectContext,\n updated: Set<string>,\n removed: Set<string>,\n): void {\n const existing = readJson(nodePath.join(ctx.cwd, action.path)) as\n Record<string, unknown> | undefined;\n if (existing === undefined) return;\n const unmerged = action.definition.unmerge(existing, ctx);\n const remainingKeys = Object.keys(unmerged).filter(key => unmerged[key] !== undefined);\n if (action.definition.removeFileIfEmpty && remainingKeys.length === 0) {\n removed.add(action.path);\n } else if (JSON.stringify(existing) !== JSON.stringify(unmerged)) {\n updated.add(action.path);\n }\n}\n\nfunction planTextPatchChange(\n action: Extract<Action, { type: 'text-patch' }>,\n ctx: ProjectContext,\n created: Set<string>,\n updated: Set<string>,\n): void {\n const original = readFileSafe(nodePath.join(ctx.cwd, action.path));\n const patched = computePatchedContent(original ?? '', action.definition);\n if (patched === original) return;\n if (original === undefined) created.add(action.path);\n else updated.add(action.path);\n}\n\nfunction planTextUnpatchChange(\n action: Extract<Action, { type: 'text-unpatch' }>,\n ctx: ProjectContext,\n updated: Set<string>,\n removed: Set<string>,\n): void {\n const original = readFileSafe(nodePath.join(ctx.cwd, action.path));\n if (!original) return;\n const unpatched = computeUnpatchedContent(original, action.definition);\n if (unpatched === original) return;\n if (shouldRemoveTextPatchTarget(unpatched, action.definition)) removed.add(action.path);\n else updated.add(action.path);\n}\n\n// ============================================================================\n// Plan execution\n// ============================================================================\n\nexport interface ExecutionResult {\n created: string[];\n updated: string[];\n removed: string[];\n warnings: string[];\n}\n\nexport class ReconcileExecutionError extends Error {\n readonly partial: ExecutionResult;\n readonly completedActions: readonly Action[];\n\n constructor(cause: unknown, partial: ExecutionResult, completedActions: readonly Action[]) {\n super(cause instanceof Error ? cause.message : String(cause));\n this.name = 'ReconcileExecutionError';\n this.partial = partial;\n this.completedActions = completedActions;\n }\n}\n\nexport function executeReconciliationActions(\n actions: readonly Action[],\n ctx: ProjectContext,\n): ExecutionResult {\n const created: string[] = [];\n const updated: string[] = [];\n const removed: string[] = [];\n const warnings: string[] = [];\n const result = { created, updated, removed, warnings };\n const completedActions: Action[] = [];\n\n for (const action of actions) {\n const targets = action.type === 'chmod' ? action.paths : [action.path];\n const before = new Map(\n targets.map(target => [target, observePath(nodePath.join(ctx.cwd, target))]),\n );\n try {\n executeAction(action, ctx, result);\n for (const target of targets) {\n recordObservedChange(\n target,\n before.get(target),\n observePath(nodePath.join(ctx.cwd, target)),\n result,\n );\n }\n completedActions.push(action);\n } catch (executionError) {\n throw new ReconcileExecutionError(executionError, result, completedActions);\n }\n }\n\n return result;\n}\n\nfunction executePlan(plan: ReconcilePlan, ctx: ProjectContext): ExecutionResult {\n return executeReconciliationActions(plan.actions, ctx);\n}\n\nfunction observePath(path: string): string | undefined {\n try {\n const stat = lstatSync(path);\n if (stat.isSymbolicLink()) return `link:${stat.mode}:${readlinkSync(path)}`;\n if (stat.isDirectory()) return `directory:${stat.mode}`;\n if (stat.isFile()) return `file:${stat.mode}:${readFile(path)}`;\n return `other:${stat.mode}`;\n } catch {\n return undefined;\n }\n}\n\nfunction recordObservedChange(\n target: string,\n before: string | undefined,\n after: string | undefined,\n result: ExecutionResult,\n): void {\n if (before === after) return;\n let changes = result.updated;\n if (before === undefined) changes = result.created;\n else if (after === undefined) changes = result.removed;\n if (!changes.includes(target)) changes.push(target);\n}\n\nfunction executeChmod(cwd: string, paths: string[]): void {\n for (const path of paths) {\n const fullPath = nodePath.join(cwd, path);\n if (exists(fullPath)) makeScriptsExecutable(fullPath);\n }\n}\n\nfunction executeRmdir(cwd: string, path: string): void {\n removeIfEmpty(nodePath.join(cwd, path));\n}\n\nfunction executeAction(action: Action, ctx: ProjectContext, result: ExecutionResult): void {\n switch (action.type) {\n case 'mkdir': {\n ensureDirectory(nodePath.join(ctx.cwd, action.path));\n break;\n }\n case 'rmdir': {\n executeRmdir(ctx.cwd, action.path);\n break;\n }\n case 'write': {\n executeWrite(ctx.cwd, action.path, action.content);\n break;\n }\n case 'rm': {\n executeFileRemoval(ctx.cwd, action.path, result);\n break;\n }\n case 'chmod': {\n executeChmod(ctx.cwd, action.paths);\n break;\n }\n case 'json-merge': {\n executeJsonMerge(ctx.cwd, action.path, action.definition, ctx, result);\n break;\n }\n case 'json-unmerge': {\n executeJsonUnmerge(ctx.cwd, action.path, action.definition, ctx);\n break;\n }\n case 'text-patch': {\n executeTextPatch(ctx.cwd, action.path, action.definition);\n break;\n }\n case 'text-unpatch': {\n executeTextUnpatch(ctx.cwd, action.path, action.definition);\n break;\n }\n }\n}\n\nfunction executeFileRemoval(cwd: string, path: string, result: ExecutionResult): void {\n const fullPath = nodePath.join(cwd, path);\n const stat = lstatIfExists(fullPath);\n if (stat === undefined) return;\n if (!isRemovableFile(stat)) {\n result.warnings.push(`Skipped file removal because ${path} is not a file or symlink.`);\n return;\n }\n\n try {\n // unlinkSync never recurses, so a path swapped to a directory after the\n // lstat check remains intact rather than being removed by a recursive rm.\n unlinkSync(fullPath);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ENOTDIR') return;\n if (code === 'EISDIR' || code === 'EPERM') {\n result.warnings.push(`Skipped file removal because ${path} is no longer a file or symlink.`);\n return;\n }\n throw error;\n }\n}\n\nfunction executeWrite(cwd: string, path: string, content: string): void {\n writeFile(nodePath.join(cwd, path), content);\n}\n\n// ============================================================================\n// Helper functions\n// ============================================================================\n\nfunction resolveFileContent(definition: FileDefinition, ctx: ProjectContext): string | undefined {\n if (definition.generator) {\n // Generator decides (undefined = skip file). Takes precedence over\n // `template` so an entry can declare its template provenance (for the\n // schema↔templates contract) while gating on project context (56JCFZ).\n return definition.generator(ctx);\n }\n\n if (definition.template) {\n const templatesDirectory = getTemplatesDirectory();\n return readFile(nodePath.join(templatesDirectory, definition.template));\n }\n\n if (definition.content) {\n return typeof definition.content === 'function' ? definition.content() : definition.content;\n }\n\n throw new Error('FileDefinition must have template, content, or generator');\n}\n\nfunction fileNeedsUpdate(installedPath: string, newContent: string): boolean {\n if (!exists(installedPath)) return true;\n const currentContent = readFileSafe(installedPath);\n return currentContent?.trim() !== newContent.trim();\n}\n\nexport function computePackagesToInstall(\n schema: SafewordSchema,\n projectType: ProjectType,\n installedDevelopmentDependencies: Record<string, string>,\n cwd?: string,\n): string[] {\n // Combine base packages with conditional packages\n const needed = [\n ...schema.packages.base,\n ...getConditionalPackages(schema.packages.conditional, projectType),\n ];\n\n // Skip packages provided by workspace members (prevents circular self-install in monorepos)\n const workspaceMembers = cwd ? getWorkspacePackageNames(cwd) : new Set<string>();\n\n // Strip version specifier (e.g. 'eslint@^9' → 'eslint') when checking installed deps\n return needed.filter(pkg => {\n const name = stripVersionSpecifier(pkg);\n return !workspaceMembers.has(name) && !Object.hasOwn(installedDevelopmentDependencies, name);\n });\n}\n\nfunction computePackagesToRemove(\n schema: SafewordSchema,\n projectType: ProjectType,\n installedDevelopmentDependencies: Record<string, string>,\n): string[] {\n const safewordPackages = [\n ...schema.packages.base,\n ...getConditionalPackages(schema.packages.conditional, projectType),\n ];\n\n // Only remove packages that are actually installed\n // Strip version specifier (e.g. 'eslint@^9' → 'eslint') when checking installed deps\n return safewordPackages.filter(pkg =>\n Object.hasOwn(installedDevelopmentDependencies, stripVersionSpecifier(pkg)),\n );\n}\n\n/**\n * Strip version specifier from a package name.\n * e.g. 'eslint@^9' → 'eslint', 'safeword' → 'safeword', '@next/eslint-plugin-next@^16' → '@next/eslint-plugin-next'\n */\nfunction stripVersionSpecifier(pkg: string): string {\n // Scoped packages start with @ — find the version @ after the scope\n const atIndex = pkg.startsWith('@') ? pkg.indexOf('@', 1) : pkg.indexOf('@');\n return atIndex === -1 ? pkg : pkg.slice(0, atIndex);\n}\n\nfunction executeJsonMerge(\n cwd: string,\n path: string,\n definition: JsonMergeDefinition,\n ctx: ProjectContext,\n result: ExecutionResult,\n): void {\n const fullPath = nodePath.join(cwd, path);\n const rawExisting = readJson(fullPath) as Record<string, unknown> | undefined;\n\n if (!rawExisting) {\n // A present-but-unparseable target (JSONC comments, a trailing comma, or genuine\n // malformation) reads as undefined just like an absent file. Skipping is correct —\n // overwriting would clobber the customer's config — but doing it silently hides the\n // merge that never landed (the #262 commented-`.markdownlint-cli2.jsonc` case, and the\n // same trap for any jsonMerge target). Distinguish the two: warn when the file exists,\n // stay silent when it's genuinely absent.\n if (exists(fullPath)) {\n result.warnings.push(\n `Skipped merging safeword config into ${path}: the file exists but could not be read as JSON ` +\n `(it may contain JSONC comments, which the merge engine does not support, or otherwise not be valid JSON). ` +\n `Add these keys manually if you need them: ${definition.keys.join(', ')}.`,\n );\n return;\n }\n // Genuinely absent: honor skipIfMissing; otherwise fall through and create it.\n if (definition.skipIfMissing) return;\n }\n\n const existing = rawExisting ?? {};\n const merged = definition.merge(existing, ctx);\n\n // Skip write if content is unchanged (avoids formatting churn)\n if (JSON.stringify(existing) === JSON.stringify(merged)) return;\n\n writeJson(fullPath, merged);\n}\n\nfunction executeJsonUnmerge(\n cwd: string,\n path: string,\n definition: JsonMergeDefinition,\n ctx: ProjectContext,\n): void {\n const fullPath = nodePath.join(cwd, path);\n if (!exists(fullPath)) return;\n\n const existing = readJson(fullPath) as Record<string, unknown> | undefined;\n if (!existing) return;\n\n const unmerged = definition.unmerge(existing, ctx);\n\n // Check if file should be removed\n if (definition.removeFileIfEmpty) {\n const remainingKeys = Object.keys(unmerged).filter(k => unmerged[k] !== undefined);\n if (remainingKeys.length === 0) {\n remove(fullPath);\n return;\n }\n }\n\n if (JSON.stringify(existing) === JSON.stringify(unmerged)) return;\n writeJson(fullPath, unmerged);\n}\n\n/**\n * The safeword-owned body lines of a re-renderable block, in order, taken from\n * the freshly-resolved content (the header/marker line excluded).\n */\nfunction rerenderBlockLines(definition: ResolvedTextPatch): string[] {\n return definition.content\n .split('\\n')\n .filter(line => line !== '' && !line.includes(definition.marker));\n}\n\n/**\n * Remove a re-renderable managed block so a fresh, ctx-resolved version can be\n * re-appended (#293). Consumes the `marker` header plus the contiguous run of\n * disk lines that match the fresh block's body lines *in order* — an on-disk\n * block is always a prefix of the new one (the owned-dir set only grows and its\n * order is fixed), so a customer line that breaks the sequence (even one that\n * coincidentally equals an owned dir) is never consumed.\n */\nfunction stripRerenderBlock(content: string, definition: ResolvedTextPatch): string {\n const lines = content.split('\\n');\n const startIndex = lines.findIndex(line => line.includes(definition.marker));\n if (startIndex === -1) return content;\n\n const blockLines = rerenderBlockLines(definition);\n let endIndex = startIndex;\n let expected = 0;\n while (\n endIndex + 1 < lines.length &&\n expected < blockLines.length &&\n lines[endIndex + 1] === blockLines[expected]\n ) {\n endIndex += 1;\n expected += 1;\n }\n\n // Also drop a single blank separator line immediately before the header.\n const blockStart = startIndex > 0 && lines[startIndex - 1] === '' ? startIndex - 1 : startIndex;\n lines.splice(blockStart, endIndex - blockStart + 1);\n return lines.join('\\n').replaceAll(/\\n{3,}/g, '\\n\\n');\n}\n\nfunction executeTextPatch(cwd: string, path: string, definition: ResolvedTextPatch): void {\n // rerender re-appends the block at EOF, so it only makes sense for `append`.\n if (definition.rerender && definition.operation !== 'append') {\n throw new Error(`rerender text patch ${path} must use operation: 'append'`);\n }\n\n const fullPath = nodePath.join(cwd, path);\n const original = readFileSafe(fullPath) ?? '';\n const patched = computePatchedContent(original, definition);\n if (patched !== original) writeFile(fullPath, patched);\n}\n\nfunction computePatchedContent(original: string, definition: ResolvedTextPatch): string {\n // Supersede: byte-exact strip of the legacy block this patch replaces (a no-op\n // when absent), so a customized managed file migrates the old block to `content`\n // on upgrade. Runs before the marker check so the swap still applies when the\n // replacement block is being added for the first time.\n const content =\n definition.supersedes === undefined ? original : original.replace(definition.supersedes, '');\n\n // Already patched: re-render/heal in place, persisting any supersede strip.\n if (content.includes(definition.marker)) {\n return healAlreadyPatchedContent(content, definition);\n }\n\n // Apply patch (this write also persists any supersede strip above).\n return definition.operation === 'prepend'\n ? definition.content + content\n : content + definition.content;\n}\n\n// The marker is already present. Re-render a drifted managed block (#293), heal a\n// legacy `---#` separator artifact from safeword <=0.30.1, and/or persist a\n// supersede strip. `content` is the post-supersede on-disk text.\nfunction healAlreadyPatchedContent(content: string, definition: ResolvedTextPatch): string {\n // Re-renderable block (#293): when the resolved content has drifted from what's\n // on disk (e.g. a custom paths.projectRoot was added), replace the managed block\n // in place so existing installs heal on upgrade. A no-op when already current.\n if (definition.rerender && !content.includes(definition.content)) {\n return stripRerenderBlock(content, definition) + definition.content;\n }\n // Heal the bare-`---` separator that glued to a heading (`---# CLAUDE.md`) in\n // files corrupted by past installs. Narrowly scoped: the marker proves we\n // authored the block, and only the exact artifact is repaired.\n let healed = content;\n if (healed.includes('\\n\\n---#')) {\n healed = healed.replaceAll('\\n\\n---#', '\\n\\n---\\n\\n#');\n }\n // Persist if a heal and/or supersede strip changed the file (the latter is rare\n // here — a legacy block lingering alongside its replacement).\n return healed;\n}\n\nfunction computeUnpatchedContent(content: string, definition: ResolvedTextPatch): string {\n let unpatched = removeExactTextPatchContent(content, definition);\n\n // Re-render patches (#293): the on-disk block can differ from the ctx-resolved\n // content (e.g. paths.projectRoot changed since install), so an exact removal\n // misses it. Fall back to the marker-anchored, sequence-bounded strip — which\n // never consumes a customer line that follows the block.\n if (unpatched === content && definition.rerender) {\n unpatched = stripRerenderBlock(content, definition);\n }\n\n // Legacy prepend blocks may have a malformed separator (`---# Heading`) from\n // safeword <=0.30.1. If the file starts with our managed marker, remove the\n // whole preamble through the first separator and preserve the customer text.\n if (unpatched === content && content.startsWith(definition.marker)) {\n const separatorIndex = content.indexOf('\\n---');\n if (separatorIndex !== -1) {\n unpatched = content.slice(separatorIndex + '\\n---'.length).replace(/^\\n+/, '');\n }\n }\n\n const firstLine = content.split('\\n', 1)[0] ?? '';\n if (unpatched === content && firstLine.includes(definition.marker)) {\n unpatched = content.split('\\n').slice(1).join('\\n').replace(/^\\n+/, ''); // drop leading blanks\n unpatched = stripLeadingSafewordSeparator(unpatched);\n }\n\n return unpatched;\n}\n\nfunction executeTextUnpatch(cwd: string, path: string, definition: ResolvedTextPatch): void {\n const fullPath = nodePath.join(cwd, path);\n const content = readFileSafe(fullPath);\n if (!content) return;\n\n const unpatched = computeUnpatchedContent(content, definition);\n\n if (shouldRemoveTextPatchTarget(unpatched, definition)) {\n remove(fullPath);\n return;\n }\n\n if (unpatched !== content) writeFile(fullPath, unpatched);\n}\n\nfunction removeExactTextPatchContent(content: string, definition: ResolvedTextPatch): string {\n let unpatched = content.replace(definition.content, '');\n const extraContents = definition.unpatchContent ?? [];\n for (const extraContent of extraContents) {\n unpatched = unpatched.replace(extraContent, '');\n }\n return unpatched;\n}\n\nfunction containsTextPatchContent(content: string, definition: TextPatchDefinition): boolean {\n return (\n content.includes(definition.marker) ||\n (definition.unpatchContent?.some(extraContent => content.includes(extraContent)) ?? false)\n );\n}\n\nfunction shouldRemoveTextPatchTarget(content: string, definition: TextPatchDefinition): boolean {\n const trimmed = content.trim();\n return (\n definition.removeFileIfContentEquals?.some(candidate => trimmed === candidate.trim()) ?? false\n );\n}\n","/**\n * Rust language pack setup\n *\n * Handles Rust-specific setup logic including:\n * - Cargo.toml [lints.clippy] merge\n */\n\nimport {\n existsSync,\n lstatSync,\n readdirSync,\n readFileSync,\n realpathSync,\n writeFileSync,\n} from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { parse } from 'smol-toml';\n\nimport type { SetupResult } from '../types.js';\n\n// Default lint configuration for Cargo.toml\nconst SAFEWORD_CLIPPY_LINTS = `[lints.clippy]\n# Enable pedantic for stricter linting (priority -1 allows individual overrides)\npedantic = { level = \"deny\", priority = -1 }\n\n# Cherry-picked restriction lints for LLM enforcement\nunwrap_used = \"deny\"\nexpect_used = \"deny\"\ntodo = \"deny\"\n\n# Allow common pedantic noise\nmissing_errors_doc = \"allow\"\nmissing_panics_doc = \"allow\"\nmodule_name_repetitions = \"allow\"\n\n[lints.rust]\n# Deny unsafe code by default (LLMs shouldn't write unsafe)\nunsafe_code = \"deny\"\n`;\n\n/**\n * Regex to extract package name from Cargo.toml.\n * Matches: [package] ... name = \"package-name\"\n * Captures the package name in group 1.\n *\n * Note: This regex is also defined in templates/hooks/lib/lint.ts because\n * template files must be self-contained (they're copied to user projects).\n */\nconst CARGO_PACKAGE_NAME_REGEX = /\\[package\\][^[]*name\\s*=\\s*\"([^\"]+)\"/;\n\nfunction isContainedPath(root: string, candidate: string): boolean {\n const relative = nodePath.relative(root, candidate);\n return (\n relative === '' || (!relative.startsWith(`..${nodePath.sep}`) && !nodePath.isAbsolute(relative))\n );\n}\n\nfunction containedWorkspaceMember(cwd: string, member: string): string | undefined {\n if (nodePath.isAbsolute(member)) return undefined;\n const root = nodePath.resolve(cwd);\n const candidate = nodePath.resolve(root, member);\n if (!isContainedPath(root, candidate)) return undefined;\n if (existsSync(candidate) && !isContainedPath(realpathSync(root), realpathSync(candidate))) {\n return undefined;\n }\n return nodePath.relative(root, candidate);\n}\n\nfunction isSafeCargoManifest(cwd: string, cargoPath: string): boolean {\n if (!existsSync(cargoPath)) return false;\n const stat = lstatSync(cargoPath);\n return (\n stat.isFile() &&\n !stat.isSymbolicLink() &&\n isContainedPath(realpathSync(cwd), realpathSync(cargoPath))\n );\n}\n\nconst SAFEWORD_WORKSPACE_LINTS = `[workspace.lints.clippy]\n# Enable pedantic for stricter linting (priority -1 allows individual overrides)\npedantic = { level = \"deny\", priority = -1 }\n\n# Cherry-picked restriction lints for LLM enforcement\nunwrap_used = \"deny\"\nexpect_used = \"deny\"\ntodo = \"deny\"\n\n# Allow common pedantic noise\nmissing_errors_doc = \"allow\"\nmissing_panics_doc = \"allow\"\nmodule_name_repetitions = \"allow\"\n\n[workspace.lints.rust]\n# Deny unsafe code by default (LLMs shouldn't write unsafe)\nunsafe_code = \"deny\"\n`;\n\n/**\n * Detect the Rust package name for a given file path.\n *\n * Walks up from the file's directory to find the nearest Cargo.toml\n * with a [package] section, then extracts the package name.\n *\n * @param filePath - Absolute path to a .rs file\n * @param cwd - Project root directory (to stop walking)\n * @returns Package name, or undefined if not found\n */\nexport function detectRustPackage(filePath: string, cwd: string): string | undefined {\n // Start from the file's directory and walk up\n let currentDirectory = nodePath.dirname(filePath);\n\n // Normalize paths for comparison\n const normalizedCwd = nodePath.resolve(cwd);\n\n while (isContainedPath(normalizedCwd, currentDirectory)) {\n if (\n existsSync(currentDirectory) &&\n !isContainedPath(realpathSync(normalizedCwd), realpathSync(currentDirectory))\n ) {\n return undefined;\n }\n const cargoPath = nodePath.join(currentDirectory, 'Cargo.toml');\n\n if (isSafeCargoManifest(normalizedCwd, cargoPath)) {\n const content = readFileSync(cargoPath, 'utf8');\n\n // Check if this Cargo.toml has a [package] section\n if (content.includes('[package]')) {\n const nameMatch = CARGO_PACKAGE_NAME_REGEX.exec(content);\n return nameMatch?.[1];\n }\n }\n\n // Move up one directory\n const parentDirectory = nodePath.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) break; // Reached filesystem root\n currentDirectory = parentDirectory;\n }\n\n return undefined;\n}\n\n/**\n * Detect workspace type from Cargo.toml content\n */\nexport function detectWorkspaceType(\n cargoContent: string,\n): 'virtual' | 'root-package' | 'single-crate' {\n const hasWorkspace = hasExactTableHeader(cargoContent, 'workspace');\n const hasPackage = hasExactTableHeader(cargoContent, 'package');\n\n if (hasWorkspace && !hasPackage) return 'virtual';\n if (hasWorkspace && hasPackage) return 'root-package';\n return 'single-crate';\n}\n\nfunction hasExactTableHeader(content: string, table: string): boolean {\n let multilineDelimiter: '\"\"\"' | \"'''\" | undefined;\n const header = `[${table}]`;\n for (const line of content.split(/\\r?\\n/u)) {\n if (multilineDelimiter !== undefined) {\n if (hasOddOccurrences(line, multilineDelimiter)) multilineDelimiter = undefined;\n continue;\n }\n const trimmed = line.trim();\n if (trimmed === header || trimmed.startsWith(`${header} #`)) return true;\n multilineDelimiter = openingMultilineDelimiter(line);\n }\n return false;\n}\n\nfunction hasOddOccurrences(line: string, delimiter: string): boolean {\n return line.split(delimiter).length % 2 === 0;\n}\n\nfunction openingMultilineDelimiter(line: string): '\"\"\"' | \"'''\" | undefined {\n const candidates = ['\"\"\"', \"'''\"] as const;\n return candidates\n .filter(delimiter => hasOddOccurrences(line, delimiter))\n .toSorted((left, right) => line.indexOf(left) - line.indexOf(right))[0];\n}\n\n/**\n * Check if Cargo.toml already has lint configuration\n */\nfunction hasExistingLints(cargoContent: string): boolean {\n const manifest = parseCargoManifest(cargoContent);\n const workspace = isTomlTable(manifest?.workspace) ? manifest.workspace : undefined;\n return isTomlTable(manifest?.lints) || isTomlTable(workspace?.lints);\n}\n\ntype TomlTable = Record<string, unknown>;\n\nfunction isTomlTable(value: unknown): value is TomlTable {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction parseCargoManifest(content: string): TomlTable | undefined {\n try {\n const manifest = parse(content);\n return isTomlTable(manifest) ? manifest : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Cargo manifests the Rust pack will update, without changing the project. */\nexport function rustToolingTargets(cwd: string): string[] {\n const rootTarget = 'Cargo.toml';\n const cargoPath = nodePath.join(cwd, rootTarget);\n if (!isSafeCargoManifest(cwd, cargoPath)) return [];\n const content = readFileSync(cargoPath, 'utf8');\n const targets = hasExistingLints(content) ? [] : [rootTarget];\n if (detectWorkspaceType(content) === 'single-crate') return targets;\n for (const member of parseWorkspaceMembers(content, cwd)) {\n const target = nodePath.join(member, 'Cargo.toml');\n const memberPath = nodePath.join(cwd, target);\n if (!isSafeCargoManifest(cwd, memberPath)) continue;\n if (!hasExistingLints(readFileSync(memberPath, 'utf8'))) targets.push(target);\n }\n return targets;\n}\n\n/**\n * Expand a single member pattern (handles glob patterns like \"crates/*\")\n */\nfunction expandMemberPattern(cwd: string, pattern: string): string[] {\n // Simple glob: only handle trailing /* (most common Cargo workspace pattern)\n if (pattern.endsWith('/*')) {\n const baseDirectory = containedWorkspaceMember(cwd, pattern.slice(0, -2));\n if (baseDirectory === undefined) return [];\n const fullPath = nodePath.join(cwd, baseDirectory);\n\n if (!existsSync(fullPath)) return [];\n\n try {\n const entries = readdirSync(fullPath, { withFileTypes: true });\n return entries\n .filter(entry => {\n if (!entry.isDirectory()) return false;\n // Only include directories that have Cargo.toml (are crates)\n const cargoPath = nodePath.join(fullPath, entry.name, 'Cargo.toml');\n return existsSync(cargoPath);\n })\n .map(entry => nodePath.join(baseDirectory, entry.name));\n } catch {\n return [];\n }\n }\n\n // Not a glob pattern, return as-is\n const member = containedWorkspaceMember(cwd, pattern);\n return member === undefined ? [] : [member];\n}\n\n/**\n * Parse workspace members from Cargo.toml\n * Supports both inline array and multi-line formats:\n * - members = [\"crates/a\", \"crates/b\"]\n * - members = [\\n \"crates/a\",\\n \"crates/b\"\\n]\n *\n * Also expands simple glob patterns like \"crates/*\"\n *\n * @param cargoContent - Content of root Cargo.toml\n * @param cwd - Project root directory (needed for glob expansion)\n * @returns Array of expanded member paths\n */\nexport function parseWorkspaceMembers(cargoContent: string, cwd: string): string[] {\n const manifest = parseCargoManifest(cargoContent);\n const members = isTomlTable(manifest?.workspace) ? manifest.workspace.members : undefined;\n if (!Array.isArray(members)) return [];\n const rawMembers = members.filter((member): member is string => typeof member === 'string');\n\n // Expand glob patterns\n const expandedMembers: string[] = [];\n for (const member of rawMembers) {\n expandedMembers.push(...expandMemberPattern(cwd, member));\n }\n\n return expandedMembers;\n}\n\n/**\n * Add [lints] workspace = true to a member crate's Cargo.toml\n */\nfunction addWorkspaceLints(cwd: string, memberCargoPath: string): void {\n if (!isSafeCargoManifest(cwd, memberCargoPath)) return;\n\n const content = readFileSync(memberCargoPath, 'utf8');\n\n // Skip if already has any lints configuration\n if (hasExistingLints(content)) return;\n\n const lintsSection = `[lints]\nworkspace = true\n`;\n\n const newContent = `${content.trimEnd()}\\n\\n${lintsSection}`;\n writeFileSync(memberCargoPath, newContent, 'utf8');\n}\n\n/**\n * Add lint configuration to root Cargo.toml if not present\n */\nfunction addRootLints(cargoPath: string, content: string, isWorkspace: boolean): void {\n if (hasExistingLints(content)) return;\n\n const lintsToAdd = isWorkspace ? SAFEWORD_WORKSPACE_LINTS : SAFEWORD_CLIPPY_LINTS;\n const newContent = `${content.trimEnd()}\\n\\n${lintsToAdd}`;\n writeFileSync(cargoPath, newContent, 'utf8');\n}\n\n/**\n * Add lints.workspace = true to all workspace member crates\n */\nfunction addMemberLints(cwd: string, content: string): void {\n const members = parseWorkspaceMembers(content, cwd);\n for (const member of members) {\n const memberCargoPath = nodePath.join(cwd, member, 'Cargo.toml');\n addWorkspaceLints(cwd, memberCargoPath);\n }\n}\n\n/**\n * Set up Rust tooling configuration.\n *\n * @param cwd - Project root directory\n * @returns Setup result\n */\nexport function setupRustTooling(cwd: string): SetupResult {\n const cargoPath = nodePath.join(cwd, 'Cargo.toml');\n if (!isSafeCargoManifest(cwd, cargoPath)) return { files: [] };\n\n // Read Cargo.toml once\n const content = readFileSync(cargoPath, 'utf8');\n const workspaceType = detectWorkspaceType(content);\n const isWorkspace = workspaceType !== 'single-crate';\n\n // Add lint configuration to root\n addRootLints(cargoPath, content, isWorkspace);\n\n // For workspaces, add lints.workspace = true to member crates\n if (isWorkspace) {\n addMemberLints(cwd, content);\n }\n\n return { files: [] };\n}\n","/**\n * Pack Config Tracking\n *\n * Helpers to read/write installedPacks in .safeword/config.json\n */\n\nimport nodePath from 'node:path';\nimport process from 'node:process';\n\nimport { readFileSafe, writeFile } from '../utils/fs.js';\n\nconst CONFIG_PATH = '.safeword/config.json';\n\ninterface SafewordConfig {\n installedPacks: string[];\n autoUpgrade?: boolean;\n /** SQL pack options — `fix: true` opts in to edit-time `sqlfluff fix` (#638). */\n sql?: { fix?: boolean };\n}\n\nfunction readConfig(cwd: string): SafewordConfig | undefined {\n const configPath = nodePath.join(cwd, CONFIG_PATH);\n const content = readFileSafe(configPath);\n if (!content) return undefined;\n return JSON.parse(content) as SafewordConfig;\n}\n\nfunction writeConfig(cwd: string, config: SafewordConfig): void {\n const configPath = nodePath.join(cwd, CONFIG_PATH);\n writeFile(configPath, JSON.stringify(config, undefined, 2));\n}\n\n/**\n * Check if auto-upgrade is enabled for this project.\n * Env vars override config: SAFEWORD_NO_AUTO_UPGRADE or CI disables.\n * Config file: autoUpgrade defaults to true when absent.\n */\nexport function shouldAutoUpgrade(cwd: string): boolean {\n if (process.env.SAFEWORD_NO_AUTO_UPGRADE) return false;\n if (process.env.CI) return false;\n const config = readConfig(cwd);\n return config?.autoUpgrade !== false;\n}\n\n/**\n * Get the list of installed packs from config.\n *\n * @param cwd - Project root directory\n * @returns Array of installed pack IDs, or empty array if no config\n */\nexport function getInstalledPacks(cwd: string): string[] {\n const config = readConfig(cwd);\n return config?.installedPacks ?? [];\n}\n\n/**\n * Check if a specific pack is installed.\n *\n * @param cwd - Project root directory\n * @param packId - Pack ID to check\n * @returns true if pack is in installedPacks\n */\nexport function isPackInstalled(cwd: string, packId: string): boolean {\n return getInstalledPacks(cwd).includes(packId);\n}\n\n/**\n * Add a pack to the installed packs list.\n * Creates config.json if it doesn't exist.\n */\nexport function addInstalledPack(cwd: string, packId: string): void {\n const config = readConfig(cwd) ?? { installedPacks: [] };\n\n if (!config.installedPacks.includes(packId)) {\n config.installedPacks.push(packId);\n writeConfig(cwd, config);\n }\n}\n","/**\n * Go-specific Setup Utilities\n *\n * Setup logic for Go projects.\n * Config generators are in files.ts (same pattern as TypeScript and Python).\n *\n * Future: Layer detection for depguard import rules.\n */\n\nimport type { SetupResult } from '../types.js';\n\n/**\n * Set up Go tooling configuration.\n *\n * Note: .golangci.yml is now created by the schema system (managedFiles).\n * This function exists for future Go-specific setup (layer detection).\n *\n * @returns Empty result (schema handles file creation)\n */\nexport function setupGoTooling(): SetupResult {\n // .golangci.yml is created by schema.ts managedFiles\n // Future: Add layer detection for depguard rules here\n return { files: [] };\n}\n","/**\n * Go Language Pack\n *\n * Detects Go projects. Config file (.golangci.yml) is created by schema.\n */\n\nimport { existsInTree } from '../../utils/fs.js';\nimport type { LanguagePack, SetupContext, SetupResult } from '../types.js';\nimport { setupGoTooling } from './setup.js';\n\nexport const golangPack: LanguagePack = {\n id: 'golang',\n name: 'Go',\n extensions: ['.go'],\n\n detect(cwd: string): boolean {\n return existsInTree(cwd, 'go.mod');\n },\n\n setup(_cwd: string, _ctx: SetupContext): SetupResult {\n // .golangci.yml is created by schema.ts (managedFiles)\n return setupGoTooling();\n },\n};\n","/**\n * Python Language Pack\n *\n * Wraps existing setup.ts functionality in the LanguagePack interface.\n */\n\nimport { existsInTree } from '../../utils/fs.js';\nimport type { LanguagePack, SetupContext, SetupResult } from '../types.js';\nimport { setupPythonTooling } from './setup.js';\n\nexport const pythonPack: LanguagePack = {\n id: 'python',\n name: 'Python',\n extensions: ['.py', '.pyi'],\n\n detect(cwd: string): boolean {\n return existsInTree(cwd, 'pyproject.toml') || existsInTree(cwd, 'requirements.txt');\n },\n\n setup(_cwd: string, _ctx: SetupContext): SetupResult {\n // Config files created by schema.ts (ownedFiles/managedFiles)\n return setupPythonTooling();\n },\n};\n","/**\n * Rust language pack\n *\n * Provides strict Clippy and rustfmt configuration for Rust projects.\n */\n\nimport { existsInTree } from '../../utils/fs.js';\nimport type { LanguagePack, SetupContext, SetupResult } from '../types.js';\nimport { setupRustTooling } from './setup.js';\n\nexport const rustPack: LanguagePack = {\n id: 'rust',\n name: 'Rust',\n extensions: ['.rs'],\n\n detect(cwd: string): boolean {\n return existsInTree(cwd, 'Cargo.toml');\n },\n\n setup(cwd: string, _ctx: SetupContext): SetupResult {\n return setupRustTooling(cwd);\n },\n};\n","/**\n * SQL Language Pack\n *\n * Detects SQL projects (dbt, sqlc, Flyway, Atlas, Prisma, Drizzle, etc.)\n * and configures SQLFluff for SQL linting with Jinja-aware parsing.\n */\n\nimport { existsSync, readdirSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { existsInTree } from '../../utils/fs.js';\nimport type { LanguagePack, SetupContext, SetupResult } from '../types.js';\n\n// Tier 1: Config file markers that unambiguously indicate SQL work\nconst TIER1_MARKERS = [\n 'dbt_project.yml',\n '.sqlfluff',\n 'sqlc.yaml',\n 'sqlc.yml',\n 'sqlc.json',\n 'flyway.toml',\n 'flyway.conf',\n 'atlas.hcl',\n 'liquibase.properties',\n 'schemachange-config.yml',\n];\n\n// Tier 2: Directories that indicate SQL work when they contain .sql files\nconst TIER2_DIRECTORIES = ['prisma/migrations', 'drizzle', 'db/migrations'];\n\n/** Check if a directory exists and contains at least one .sql file (checks one level of subdirs too). */\nfunction directoryHasSqlFiles(directory: string): boolean {\n if (!existsSync(directory)) return false;\n try {\n const entries = readdirSync(directory, { withFileTypes: true });\n // Check direct .sql files (drizzle/, db/migrations/)\n if (entries.some(entry => entry.isFile() && entry.name.endsWith('.sql'))) return true;\n // Check subdirectories for .sql files (prisma/migrations/20210313_init/migration.sql)\n for (const entry of entries) {\n if (!entry.isDirectory()) {\n continue;\n }\n\n const subEntries = readdirSync(nodePath.join(directory, entry.name));\n if (subEntries.some(f => f.endsWith('.sql'))) return true;\n }\n return false;\n } catch {\n return false;\n }\n}\n\nexport const sqlPack: LanguagePack = {\n id: 'sql',\n name: 'SQL',\n extensions: ['.sql'],\n\n detect(cwd: string): boolean {\n // Tier 1: config file markers (high confidence)\n for (const marker of TIER1_MARKERS) {\n if (existsInTree(cwd, marker)) return true;\n }\n\n // Tier 2: directory conventions with .sql files\n for (const directory of TIER2_DIRECTORIES) {\n if (directoryHasSqlFiles(nodePath.join(cwd, directory))) return true;\n }\n\n return false;\n },\n\n setup(_cwd: string, _ctx: SetupContext): SetupResult {\n // Config files created by schema.ts (ownedFiles/managedFiles)\n return { files: [] };\n },\n};\n","/**\n * TypeScript Language Pack\n *\n * Detects TypeScript/JavaScript projects.\n * Config files (ESLint, Prettier) are created by schema.\n */\n\nimport nodePath from 'node:path';\n\nimport { exists } from '../../utils/fs.js';\nimport type { LanguagePack, SetupContext, SetupResult } from '../types.js';\nimport { setupTypescriptTooling } from './setup.js';\n\nexport const typescriptPack: LanguagePack = {\n id: 'typescript',\n name: 'TypeScript',\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'],\n\n detect(cwd: string): boolean {\n return exists(nodePath.join(cwd, 'package.json'));\n },\n\n setup(_cwd: string, _ctx: SetupContext): SetupResult {\n // ESLint/Prettier configs created by schema.ts (ownedFiles/managedFiles)\n return setupTypescriptTooling();\n },\n};\n","/**\n * TypeScript-specific Setup Utilities\n *\n * Setup logic for TypeScript/JavaScript projects.\n * Config generators are in files.ts (same pattern as Python and Go).\n *\n * Note: ESLint/Prettier setup is handled by the schema system.\n * This file exists for consistency with other language packs.\n */\n\nimport type { SetupResult } from '../types.js';\n\n/**\n * Set up TypeScript tooling configuration.\n *\n * Note: ESLint and Prettier configs are created by the schema system\n * (ownedFiles and managedFiles). This function exists for consistency\n * with other language packs and future TypeScript-specific setup.\n *\n * @returns Empty result (schema handles file creation)\n */\nexport function setupTypescriptTooling(): SetupResult {\n // Config files created by schema.ts ownedFiles/managedFiles\n // Future: Add TypeScript-specific setup logic here\n return { files: [] };\n}\n","/**\n * Pack Registry\n *\n * Central registry of language packs with lookup helpers.\n */\n\nimport { getInstalledPacks } from './config.js';\nimport { golangPack } from './golang/index.js';\nimport { pythonPack } from './python/index.js';\nimport { rustPack } from './rust/index.js';\nimport { sqlPack } from './sql/index.js';\nimport type { LanguagePack } from './types.js';\nimport { typescriptPack } from './typescript/index.js';\n\n/**\n * All registered language packs.\n */\nexport const LANGUAGE_PACKS: Record<string, LanguagePack> = {\n sql: sqlPack,\n golang: golangPack,\n python: pythonPack,\n rust: rustPack,\n typescript: typescriptPack,\n};\n\n/**\n * Find the language pack for a file extension.\n *\n * @param ext - File extension including dot (e.g., '.py')\n * @returns The matching pack or undefined\n */\nexport function findPackForExtension(extension: string): LanguagePack | undefined {\n for (const pack of Object.values(LANGUAGE_PACKS)) {\n if (pack.extensions.includes(extension)) {\n return pack;\n }\n }\n return undefined;\n}\n\n/**\n * Detect which languages are present in a project.\n *\n * @param cwd - Project root directory\n * @returns Array of pack IDs for detected languages\n */\nexport function detectLanguages(cwd: string): string[] {\n const detected: string[] = [];\n\n for (const pack of Object.values(LANGUAGE_PACKS)) {\n if (pack.detect(cwd)) {\n detected.push(pack.id);\n }\n }\n\n return detected;\n}\n\n/**\n * Get pack IDs for detected languages that aren't installed.\n *\n * @param cwd - Project root directory\n * @returns Array of missing pack IDs\n */\nexport function getMissingPacks(cwd: string): string[] {\n const detected = detectLanguages(cwd);\n const installed = getInstalledPacks(cwd);\n return detected.filter(packId => !installed.includes(packId));\n}\n","/**\n * Project Context Utilities\n *\n * Shared helpers for creating ProjectContext objects used by reconcile().\n */\n\nimport nodePath from 'node:path';\n\nimport type { ProjectContext } from '../schema.js';\nimport { resolveNamespaceRoot } from './configured-paths.js';\nimport { readJson } from './fs.js';\nimport { isGitRepo } from './git.js';\nimport { detectHookManagerWorld } from './hook-manager.js';\nimport { detectLanguages, detectProjectType, type PackageJson } from './project-detector.js';\n\n/**\n * Create a ProjectContext from the current working directory.\n *\n * Reads package.json, detects project type and languages for use with reconcile().\n * @param cwd - Working directory\n */\nexport function createProjectContext(cwd: string): ProjectContext {\n const packageJson = readJson(nodePath.join(cwd, 'package.json')) as PackageJson | undefined;\n\n return {\n cwd,\n projectType: detectProjectType(packageJson ?? {}, cwd),\n developmentDeps: packageJson?.devDependencies ?? {},\n productionDeps: packageJson?.dependencies ?? {},\n isGitRepo: isGitRepo(cwd),\n languages: detectLanguages(cwd),\n namespaceRoot: resolveNamespaceRoot(cwd),\n hookManager: detectHookManagerWorld(cwd, {\n ...packageJson?.dependencies,\n ...packageJson?.devDependencies,\n }),\n };\n}\n","/**\n * Git utilities for CLI operations\n */\n\nimport { execFileSync } from 'node:child_process';\nimport nodePath from 'node:path';\n\nimport { exists } from './fs.js';\n\n/**\n * Check if directory is a git repository\n * @param cwd\n */\nexport function isGitRepo(cwd: string): boolean {\n return exists(nodePath.join(cwd, '.git'));\n}\n\n/** The git worktree root at/above cwd, or undefined outside any repository. */\nexport function gitToplevel(cwd: string): string | undefined {\n try {\n const top = execFileSync('git', ['rev-parse', '--show-toplevel'], {\n cwd,\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'pipe'],\n }).trim();\n return top === '' ? undefined : top;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Stop tracking files that are now gitignored but were committed before the\n * ignore rule existed (e.g. safeword's transient hook state). `git rm --cached`\n * removes them from the index only — the on-disk files, which the hooks\n * read/write directly, are left untouched. Behaviour-neutral; it just clears\n * the perpetual-dirty-tree churn that aborts branch switches. No-op outside a\n * git repo or when a pathspec matches nothing (`--ignore-unmatch`).\n * @param cwd project root\n * @param paths gitignore-relative pathspecs to untrack (globs allowed)\n */\nexport function untrackIgnoredFiles(cwd: string, paths: readonly string[]): void {\n if (!isGitRepo(cwd)) return;\n for (const pattern of paths) {\n try {\n execFileSync('git', ['rm', '--cached', '--ignore-unmatch', '--', pattern], {\n cwd,\n stdio: 'pipe',\n });\n } catch {\n // Best-effort: an un-removable path must never fail the upgrade.\n }\n }\n}\n","/**\n * Hook-manager world detection (ZJMZ50, #810 child 2).\n *\n * Decides how the boundary-gate shims reach a host repo: `husky` hosts get\n * marker-block appends into `.husky/*`; every other world gets a printed\n * integration snippet (nudge), never an edited config. The rule is\n * conservative-when-contested — safeword appends to `.husky` only when husky\n * is uncontested by another manager's config, or actively confirmed by\n * `core.hooksPath`. A wrong nudge is noise; a wrong append is dead lines in\n * someone else's hook file.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport nodePath from 'node:path';\n\nimport type { HookManagerWorld } from '../packs/types.js';\nimport { exists } from './fs.js';\n\nexport type { HookManagerWorld } from '../packs/types.js';\n\n/** Lefthook's recognized config filenames — shared with the nudge's quiesce check. */\nexport const LEFTHOOK_CONFIGS = [\n 'lefthook.yml',\n 'lefthook.yaml',\n '.lefthook.yml',\n '.lefthook.yaml',\n];\nconst PRE_COMMIT_CONFIG = '.pre-commit-config.yaml';\n\n/** The repo's configured core.hooksPath, or undefined when unset/unreadable. */\nfunction configuredHooksPath(cwd: string): string | undefined {\n try {\n const value = execFileSync('git', ['config', '--get', 'core.hooksPath'], {\n cwd,\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'pipe'],\n }).trim();\n return value === '' ? undefined : value;\n } catch {\n return undefined;\n }\n}\n\n/** True when a (relative) hooks path lives inside `.husky` — husky's signature. */\nfunction pointsIntoHusky(hooksPath: string): boolean {\n return hooksPath === '.husky' || hooksPath.startsWith('.husky/');\n}\n\n/** Which hook-manager world governs this host's git hooks. */\nexport function detectHookManagerWorld(\n cwd: string,\n allDependencies: Record<string, string>,\n): HookManagerWorld {\n const hooksPath = configuredHooksPath(cwd);\n const hasLefthook = LEFTHOOK_CONFIGS.some(name => exists(nodePath.join(cwd, name)));\n const hasPreCommit = exists(nodePath.join(cwd, PRE_COMMIT_CONFIG));\n\n // An explicit core.hooksPath is the active-manager signal and wins outright.\n if (hooksPath !== undefined) {\n if (pointsIntoHusky(hooksPath)) return 'husky';\n if (hasLefthook) return 'lefthook';\n if (hasPreCommit) return 'pre-commit';\n return 'bare';\n }\n\n // No active signal (e.g. a fresh clone before `prepare`): another manager's\n // config contests any leftover .husky dir, so nudge instead of appending.\n if (hasLefthook) return 'lefthook';\n if (hasPreCommit) return 'pre-commit';\n if (exists(nodePath.join(cwd, '.husky'))) return 'husky';\n if ('husky' in allDependencies) return 'husky-uninitialized';\n return 'bare';\n}\n","/**\n * Project type detection from package.json\n *\n * Detects frameworks and tools used in the project to configure\n * appropriate linting rules.\n */\n\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { LANGUAGE_PACKS } from '../packs/registry.js';\nimport type { Languages, ProjectType } from '../packs/types.js';\nimport { detect, SAFEWORD_PRIMARY_LINT_SCRIPT } from '../presets/typescript/detect.js';\nimport { isShippedCucumberTemplateRevision } from './cucumber-template-revisions.js';\nimport { findInTree, readFileSafe, readJson } from './fs.js';\nimport { WORKSPACE_ROOTS } from './workspaces.js';\n\nconst {\n TAILWIND_PACKAGES,\n TANSTACK_QUERY_PACKAGES,\n hasExistingLinter,\n hasExistingFormatter,\n hasExistingPrettierConfig,\n} = detect;\n\n// Python project file markers (used by detectPythonType and config detection)\nconst PYPROJECT_TOML = 'pyproject.toml';\nconst REQUIREMENTS_TXT = 'requirements.txt';\nconst UV_LOCK = 'uv.lock';\n\n// Rust config file markers\nconst CLIPPY_CONFIG_FILES = ['clippy.toml', '.clippy.toml'];\nconst RUSTFMT_CONFIG_FILES = ['rustfmt.toml', '.rustfmt.toml'];\n\n// ESLint config file markers (flat config and legacy)\nconst ESLINT_CONFIG_FILES = [\n 'eslint.config.ts',\n 'eslint.config.mts',\n 'eslint.config.mjs',\n 'eslint.config.js',\n 'eslint.config.cjs',\n '.eslintrc.js',\n '.eslintrc.cjs',\n '.eslintrc.json',\n '.eslintrc.yml',\n '.eslintrc.yaml',\n] as const;\n\n// golangci-lint config file markers\nconst GOLANGCI_CONFIG_FILES = [\n '.golangci.yml',\n '.golangci.yaml',\n '.golangci.toml',\n '.golangci.json',\n];\n\n// Python frameworks to detect (order matters - first match wins)\nconst PYTHON_FRAMEWORKS = ['django', 'flask', 'fastapi'] as const;\n\nexport interface PackageJson {\n name?: string;\n version?: string;\n private?: boolean;\n main?: string;\n module?: string;\n exports?: unknown;\n types?: string;\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n}\n\n/**\n * Python-specific detection (returned only if languages.python)\n * @see ARCHITECTURE.md → Language Detection\n */\nexport interface PythonProjectType {\n framework: 'django' | 'flask' | 'fastapi' | undefined;\n packageManager: 'poetry' | 'uv' | 'pip';\n}\n\n/**\n * Detects which languages are used in the project\n * @param cwd - Working directory to scan\n * @returns Languages object indicating which languages are present\n * @see ARCHITECTURE.md → Language Detection\n */\nexport function detectLanguages(cwd: string): Languages {\n // Delegate to each pack's detect() method — single source of truth.\n // TypeScript pack checks root-only (subdirectory package.json is too common).\n // All other packs use recursive tree scanning via existsInTree().\n const hasLanguage = (key: string): boolean => LANGUAGE_PACKS[key]?.detect(cwd) ?? false;\n return {\n javascript: hasLanguage('typescript'),\n python: hasLanguage('python'),\n golang: hasLanguage('golang'),\n rust: hasLanguage('rust'),\n sql: hasLanguage('sql'),\n };\n}\n\n/**\n * Detects Python project type (framework and package manager)\n * @param cwd - Working directory to scan\n * @returns PythonProjectType or undefined if not a Python project\n * @see ARCHITECTURE.md → Language Detection\n */\nexport function detectPythonType(cwd: string): PythonProjectType | undefined {\n // Search root and immediate subdirectories for Python manifests\n const pyprojectDirectory = findInTree(cwd, PYPROJECT_TOML);\n const requirementsDirectory = findInTree(cwd, REQUIREMENTS_TXT);\n\n // Determine manifest directory — pyproject.toml takes priority\n const manifestDirectory = pyprojectDirectory ?? requirementsDirectory;\n if (!manifestDirectory) {\n return undefined;\n }\n\n // Read project file for dependency/tool detection\n const content = readFileSync(\n pyprojectDirectory\n ? nodePath.join(pyprojectDirectory, PYPROJECT_TOML)\n : nodePath.join(manifestDirectory, REQUIREMENTS_TXT),\n 'utf8',\n );\n\n // Detect package manager (priority: poetry > uv > pip)\n let packageManager: PythonProjectType['packageManager'] = 'pip';\n if (pyprojectDirectory && content.includes('[tool.poetry]')) {\n packageManager = 'poetry';\n } else if (existsSync(nodePath.join(manifestDirectory, UV_LOCK))) {\n packageManager = 'uv';\n }\n\n // Detect framework (first match wins)\n const contentLower = content.toLowerCase();\n const framework = PYTHON_FRAMEWORKS.find(fw => contentLower.includes(fw));\n\n return { framework, packageManager };\n}\n\n/**\n * Checks if a directory contains any .sh files up to specified depth.\n * Excludes dependency, VCS, and agent-configuration directories.\n * @param cwd\n * @param maxDepth\n */\nfunction hasShellScripts(cwd: string, maxDepth = 4): boolean {\n const excludeDirectories = new Set([\n 'node_modules',\n '.git',\n '.agents',\n '.claude',\n '.codex',\n '.cursor',\n '.safeword',\n ]);\n\n function scan(dir: string, depth: number): boolean {\n if (depth > maxDepth) return false;\n\n try {\n const entries = readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isFile() && entry.name.endsWith('.sh')) {\n return true;\n }\n if (\n entry.isDirectory() &&\n !excludeDirectories.has(entry.name) &&\n scan(nodePath.join(dir, entry.name), depth + 1)\n ) {\n return true;\n }\n }\n } catch {\n // Ignore permission errors\n }\n return false;\n }\n\n return scan(cwd, 0);\n}\n\ninterface PackageJsonWithScripts extends PackageJson {\n scripts?: Record<string, string>;\n}\n\n/** Returns the first file from candidates that exists in cwd, or undefined. */\nfunction findFirstExisting(cwd: string, candidates: readonly string[]): string | undefined {\n return candidates.find(file => existsSync(nodePath.join(cwd, file)));\n}\n\n/**\n * Check if project has existing ESLint config.\n * @param cwd - Working directory to scan\n * @returns The config file path if found, undefined otherwise.\n */\nfunction findExistingEslintConfig(cwd: string): string | undefined {\n return findFirstExisting(cwd, ESLINT_CONFIG_FILES);\n}\n\n/**\n * Find existing Ruff config location.\n * @param cwd - Working directory to scan\n * @returns 'ruff.toml' if standalone config exists, 'pyproject.toml' if [tool.ruff] exists, undefined if none\n */\nfunction findExistingRuffConfig(cwd: string): 'ruff.toml' | 'pyproject.toml' | undefined {\n // Check for standalone ruff.toml first (takes precedence)\n if (existsSync(nodePath.join(cwd, 'ruff.toml'))) return 'ruff.toml';\n\n // Check for [tool.ruff] in pyproject.toml\n return fileContainsSection(nodePath.join(cwd, PYPROJECT_TOML), '[tool.ruff]')\n ? 'pyproject.toml'\n : undefined;\n}\n\n/**\n * Check if project has existing mypy config.\n * @param cwd - Working directory to scan\n * @returns True if mypy.ini/.mypy.ini exists OR [tool.mypy] in pyproject.toml\n */\nfunction hasExistingMypyConfig(cwd: string): boolean {\n // Check for standalone mypy config files\n if (existsSync(nodePath.join(cwd, 'mypy.ini'))) return true;\n if (existsSync(nodePath.join(cwd, '.mypy.ini'))) return true;\n\n // Check for [tool.mypy] in pyproject.toml\n return fileContainsSection(nodePath.join(cwd, PYPROJECT_TOML), '[tool.mypy]');\n}\n\n/**\n * Check if project has existing import-linter config.\n * @param cwd - Working directory to scan\n * @returns True if .importlinter exists OR [tool.importlinter] in pyproject.toml\n */\nfunction hasExistingImportLinterConfig(cwd: string): boolean {\n // Check for standalone .importlinter file\n if (existsSync(nodePath.join(cwd, '.importlinter'))) return true;\n\n // Check for [importlinter] in setup.cfg (import-linter's third auto-detected form)\n if (fileContainsSection(nodePath.join(cwd, 'setup.cfg'), '[importlinter]')) return true;\n\n // Check for [tool.importlinter] in pyproject.toml\n return fileContainsSection(nodePath.join(cwd, PYPROJECT_TOML), '[tool.importlinter]');\n}\n\n/** True when `filePath` exists and contains `section`; false on absence or read error. */\nfunction fileContainsSection(filePath: string, section: string): boolean {\n if (!existsSync(filePath)) return false;\n try {\n return readFileSync(filePath, 'utf8').includes(section);\n } catch {\n return false;\n }\n}\n\n// SQLFluff config file markers\nconst SQLFLUFF_CONFIG_FILES = ['.sqlfluff', 'setup.cfg'];\n\n/**\n * Detect JavaScript framework dependencies from package.json.\n */\nfunction detectFrameworks(\n dependencies: Record<string, string>,\n developmentDependencies: Record<string, string>,\n allDependencies: Record<string, string>,\n): Pick<\n ProjectType,\n | 'typescript'\n | 'react'\n | 'nextjs'\n | 'astro'\n | 'vitest'\n | 'playwright'\n | 'tailwind'\n | 'tanstackQuery'\n> {\n const hasNextJs = 'next' in dependencies;\n return {\n typescript: 'typescript' in allDependencies,\n react: 'react' in dependencies || 'react' in developmentDependencies || hasNextJs,\n nextjs: hasNextJs,\n astro: 'astro' in dependencies || 'astro' in developmentDependencies,\n vitest: 'vitest' in developmentDependencies,\n playwright: '@playwright/test' in developmentDependencies,\n tailwind: TAILWIND_PACKAGES.some(pkg => Object.hasOwn(allDependencies, pkg)),\n tanstackQuery: TANSTACK_QUERY_PACKAGES.some(pkg => Object.hasOwn(allDependencies, pkg)),\n };\n}\n\n/**\n * Detect if package is publishable (has entry points and not private).\n */\nfunction detectPublishable(packageJson: PackageJsonWithScripts): boolean {\n const hasEntryPoints = !!(packageJson.main || packageJson.module || packageJson.exports);\n return hasEntryPoints && packageJson.private !== true;\n}\n\nfunction hasSafewordPrimaryLinter(\n cwd: string | undefined,\n scripts: Record<string, string>,\n): boolean {\n return (\n cwd !== undefined &&\n existsSync(nodePath.join(cwd, '.safeword')) &&\n scripts.lint === SAFEWORD_PRIMARY_LINT_SCRIPT\n );\n}\n\n/**\n * Detect existing JS/TS/Python tooling configuration.\n */\nfunction detectCoreTooling(\n cwd: string | undefined,\n scripts: Record<string, string>,\n): Pick<\n ProjectType,\n | 'existingLinter'\n | 'existingFormatter'\n | 'existingPrettierConfig'\n | 'existingEslintConfig'\n | 'legacyEslint'\n | 'existingRuffConfig'\n | 'existingMypyConfig'\n | 'existingImportLinterConfig'\n> {\n const eslintConfig = cwd ? findExistingEslintConfig(cwd) : undefined;\n return {\n // Do not let the lint script generated by setup activate a conditional\n // schema branch on the next run. The installed marker distinguishes it\n // from an identical customer-owned script in an unconfigured project.\n existingLinter: hasExistingLinter(scripts) && !hasSafewordPrimaryLinter(cwd, scripts),\n existingFormatter: cwd ? hasExistingFormatter(cwd, scripts) : 'format' in scripts,\n existingPrettierConfig: cwd ? hasExistingPrettierConfig(cwd) : false,\n existingEslintConfig: eslintConfig,\n legacyEslint: eslintConfig?.startsWith('.eslintrc') ?? false,\n existingRuffConfig: cwd ? findExistingRuffConfig(cwd) : undefined,\n existingMypyConfig: cwd ? hasExistingMypyConfig(cwd) : false,\n existingImportLinterConfig: cwd ? hasExistingImportLinterConfig(cwd) : false,\n };\n}\n\n/**\n * Detect existing Go/Rust tooling configuration.\n */\nfunction detectSystemsTooling(\n cwd: string | undefined,\n): Pick<\n ProjectType,\n | 'existingGolangciConfig'\n | 'existingClippyConfig'\n | 'existingRustfmtConfig'\n | 'existingSqlfluffConfig'\n> {\n return {\n existingGolangciConfig: cwd ? findFirstExisting(cwd, GOLANGCI_CONFIG_FILES) : undefined,\n existingClippyConfig: cwd ? findFirstExisting(cwd, CLIPPY_CONFIG_FILES) : undefined,\n existingRustfmtConfig: cwd ? findFirstExisting(cwd, RUSTFMT_CONFIG_FILES) : undefined,\n existingSqlfluffConfig: cwd ? findFirstExisting(cwd, SQLFLUFF_CONFIG_FILES) : undefined,\n };\n}\n\n/**\n * Detect existing tooling configuration from file system.\n */\nfunction detectExistingTooling(\n cwd: string | undefined,\n scripts: Record<string, string>,\n): Pick<\n ProjectType,\n | 'existingLinter'\n | 'existingFormatter'\n | 'existingPrettierConfig'\n | 'existingEslintConfig'\n | 'legacyEslint'\n | 'existingRuffConfig'\n | 'existingMypyConfig'\n | 'existingImportLinterConfig'\n | 'existingGolangciConfig'\n | 'existingClippyConfig'\n | 'existingRustfmtConfig'\n | 'existingSqlfluffConfig'\n> {\n return {\n ...detectCoreTooling(cwd, scripts),\n ...detectSystemsTooling(cwd),\n };\n}\n\n/**\n * Detects project type from package.json contents and optional file scanning\n * @param packageJson - Package.json contents including scripts\n * @param cwd - Working directory for file-based detection\n */\n// JS/TS application-source detection — distinguishes a real JS project from a\n// pure Python/Go/Rust repo that only carries safeword's TS BDD lane (ticket 102b).\n// Gates JS-app-only tooling (knip, dependency-cruiser) so non-JS repos don't get it.\nconst JS_SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.mts',\n '.cts',\n]);\nconst JS_SOURCE_DIR_EXCLUDE = new Set([\n 'node_modules',\n '.git',\n '.safeword',\n 'dist',\n 'build',\n 'target',\n 'coverage',\n 'vendor',\n]);\n// Root-level lane scaffolding + safeword-owned files are NOT \"real\" JS source.\nconst LANE_DIRS = new Set(['steps', 'features']);\nconst LANE_FILES = new Set(['cucumber.mjs', 'eslint.config.mjs', '.prettierrc']);\n\n/** A directory to skip while scanning for JS source (excludes + the root-level lane). */\nfunction isExcludedSourceDirectory(name: string, depth: number): boolean {\n if (name.startsWith('.') || JS_SOURCE_DIR_EXCLUDE.has(name)) return true;\n return depth === 0 && LANE_DIRS.has(name); // root steps/ features/ are the BDD lane\n}\n\n/** A file that counts as real JS/TS application source (not root-level lane scaffolding). */\nfunction isJsSourceFile(name: string, depth: number): boolean {\n if (depth === 0 && LANE_FILES.has(name)) return false;\n return JS_SOURCE_EXTENSIONS.has(nodePath.extname(name));\n}\n\nfunction scanForJsSource(directory: string, depth: number, maxDepth: number): boolean {\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return false;\n }\n for (const entry of entries) {\n if (entry.isFile()) {\n if (isJsSourceFile(entry.name, depth)) return true;\n continue;\n }\n if (!entry.isDirectory() || depth >= maxDepth || isExcludedSourceDirectory(entry.name, depth))\n continue;\n if (scanForJsSource(nodePath.join(directory, entry.name), depth + 1, maxDepth)) return true;\n }\n return false;\n}\n\n/**\n * True if the repo has real JS/TS application source — not just safeword's TS BDD\n * lane scaffolding. Stable across `setup` and `upgrade` (it reads source, not the\n * stub `package.json`), so it can gate JS-app-only tooling off non-JS projects.\n */\nexport function hasJsSource(cwd: string, maxDepth = 6): boolean {\n return scanForJsSource(cwd, 0, maxDepth);\n}\n\n// The cucumber-js config filename safeword scaffolds its own lane as. Load-bearing\n// for own-scaffold identity: its membership in the discovery list (below), the\n// self-exclusion in detectCucumberHarnessEvidence, and the own-lane probe in\n// detectCucumberLane must all name the same file so a scaffold rename stays in lockstep.\nconst SAFEWORD_LANE_CONFIG_FILE = 'cucumber.mjs';\n\n// Cucumber-js native config discovery order (first wins). A root config with\n// any of these names is a harness safeword must not compete with — except\n// safeword's own scaffolded cucumber.mjs, excluded by content match below.\nconst CUCUMBER_CONFIG_FILES = [\n 'cucumber.json',\n 'cucumber.yaml',\n 'cucumber.yml',\n 'cucumber.js',\n 'cucumber.cjs',\n SAFEWORD_LANE_CONFIG_FILE,\n] as const;\n\n/**\n * True when the file is safeword's own lane scaffold: a hash-match against a\n * shipped template revision (so upgrades from older safewords never read\n * their own lane as a host harness). The current template is always in the\n * registry — the cucumber-template-revisions contract test enforces it — so\n * no separate byte-compare against the bundled template is needed.\n */\nfunction isSafewordLaneTemplate(configPath: string): boolean {\n const content = readFileSafe(configPath);\n return content !== undefined && isShippedCucumberTemplateRevision(content);\n}\n\nfunction manifestDependsOnCucumber(manifestPath: string): boolean {\n const manifest = readJson(manifestPath) as\n { dependencies?: Record<string, string>; devDependencies?: Record<string, string> } | undefined;\n return Boolean(\n manifest?.dependencies?.['@cucumber/cucumber'] ??\n manifest?.devDependencies?.['@cucumber/cucumber'],\n );\n}\n\n/**\n * Cucumber evidence inside one workspace package: a config file of any name\n * (no self-exclusion — safeword only ever scaffolds the lane at the root) or\n * a @cucumber/cucumber dependency.\n */\nfunction findCucumberEvidenceInPackage(cwd: string, packagePath: string): string | undefined {\n for (const name of CUCUMBER_CONFIG_FILES) {\n if (existsSync(nodePath.join(cwd, packagePath, name))) {\n return `${packagePath}/${name}`;\n }\n }\n if (manifestDependsOnCucumber(nodePath.join(cwd, packagePath, 'package.json'))) {\n return `${packagePath}/package.json (@cucumber/cucumber)`;\n }\n return undefined;\n}\n\n/** First cucumber config file or dep under `<cwd>/<root>/*`, as evidence. */\nfunction findCucumberEvidenceUnderRoot(cwd: string, root: string): string | undefined {\n let entries;\n try {\n entries = readdirSync(nodePath.join(cwd, root), { withFileTypes: true });\n } catch {\n return undefined;\n }\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const evidence = findCucumberEvidenceInPackage(cwd, `${root}/${entry.name}`);\n if (evidence !== undefined) return evidence;\n }\n return undefined;\n}\n\n/** First direct workspace package with cucumber evidence (config file or dep). */\nfunction detectWorkspaceCucumberDependency(cwd: string): string | undefined {\n for (const root of WORKSPACE_ROOTS) {\n const evidence = findCucumberEvidenceUnderRoot(cwd, root);\n if (evidence !== undefined) return evidence;\n }\n return undefined;\n}\n\nexport interface CucumberLaneDetection {\n existingCucumberHarness: string | undefined;\n scaffoldBddLane: boolean;\n}\n\n/** Host-harness evidence: first foreign config file, else workspace dep, else root dep. */\nfunction detectCucumberHarnessEvidence(cwd: string, ownLanePresent: boolean): string | undefined {\n for (const name of CUCUMBER_CONFIG_FILES) {\n const configPath = nodePath.join(cwd, name);\n if (!existsSync(configPath)) continue;\n if (name === SAFEWORD_LANE_CONFIG_FILE && ownLanePresent) continue;\n return name;\n }\n\n const workspaceEvidence = detectWorkspaceCucumberDependency(cwd);\n if (workspaceEvidence !== undefined) return workspaceEvidence;\n\n // A root dep with safeword's scaffolded lane present is safeword's own\n // install, not host evidence — otherwise every existing install would\n // self-trigger detection at upgrade (the 56JCFZ self-trigger trap).\n if (!ownLanePresent && manifestDependsOnCucumber(nodePath.join(cwd, 'package.json'))) {\n return 'package.json (@cucumber/cucumber)';\n }\n return undefined;\n}\n\n/**\n * Detect a cucumber harness safeword did not scaffold, and whether the\n * starter lane is safeword's to scaffold/maintain (ticket 56JCFZ).\n * Evidence and suppression are deliberately separate: a bitten repo (own\n * lane + host harness) keeps lane maintenance while advisories surface the\n * duplicate; a fresh repo with a host harness gets no lane at all.\n */\nexport function detectCucumberLane(cwd: string | undefined): CucumberLaneDetection {\n if (!cwd) return { existingCucumberHarness: undefined, scaffoldBddLane: true };\n\n const ownLanePresent = isSafewordLaneTemplate(nodePath.join(cwd, SAFEWORD_LANE_CONFIG_FILE));\n const evidence = detectCucumberHarnessEvidence(cwd, ownLanePresent);\n return {\n existingCucumberHarness: evidence,\n scaffoldBddLane: ownLanePresent || evidence === undefined,\n };\n}\n\nexport function detectProjectType(packageJson: PackageJsonWithScripts, cwd?: string): ProjectType {\n const dependencies = packageJson.dependencies ?? {};\n const developmentDependencies = packageJson.devDependencies ?? {};\n const allDependencies = { ...dependencies, ...developmentDependencies };\n const scripts = packageJson.scripts ?? {};\n\n return {\n ...detectFrameworks(dependencies, developmentDependencies, allDependencies),\n publishableLibrary: detectPublishable(packageJson),\n shell: cwd ? hasShellScripts(cwd) : false,\n hasJsSource: cwd ? hasJsSource(cwd) : false,\n ...detectCucumberLane(cwd),\n ...detectExistingTooling(cwd, scripts),\n };\n}\n\nexport { type Languages, type ProjectType } from '../packs/types.js';\n","/**\n * SHA-256 hashes of every shipped revision of\n * `templates/cucumber/cucumber.mjs` (ticket 56JCFZ). Harness detection\n * treats a root `cucumber.mjs` matching ANY of these as safeword's own\n * scaffold — an install from an older safeword must never be mistaken for a\n * host cucumber harness at upgrade.\n *\n * MAINTENANCE: when the template changes, append the hash of the NEW content\n * (`sha256sum packages/cli/templates/cucumber/cucumber.mjs`). A contract\n * test (cucumber-template-revisions.test.ts) fails when the current\n * template's hash is missing, so this list cannot silently rot.\n *\n * History source: `git log --follow -- packages/cli/templates/cucumber/cucumber.mjs`,\n * one entry per distinct content (oldest first).\n */\n\nimport { createHash } from 'node:crypto';\n\nconst CUCUMBER_TEMPLATE_REVISION_HASHES: ReadonlySet<string> = new Set([\n // f11d63b2 — 102a initial lane config (root features/ only)\n '1a9a1bd23c1f7b249800a631e4f3d59abc24678b9bcb1a21891a95378dbadc58',\n // a874ea92 — VM78NC workspace feature paths\n '761b20d508207b58e234fdcf592ae4cd0e916548ef46177469e2165c1e13832b',\n // 4b3aefd4 — CLI feature-path passthrough\n '3d22842f73c62e497a525dbe0a126b42bf2202fb8369ede29ba45b6d1e493fb6',\n // 56JCFZ — runtime paths.features/paths.steps config read (current)\n '929014fb4ce6ca1483b292757b6044be89932028f5bb21ba82c28ffd66b09e53',\n // 4DK9H4 — live profile excludes @manual while selecting @live scenarios\n '1295e832b1b3bdd0faa963e218bd489c9c252a981fc8e315d4814d4208384a5f',\n // ECGEP9 — automated lane distinguishes Vitest-backed proof from unfinished work\n '0ec4cb6d65b39e01f8aff27c4ca3841e0271a43b5435492676450f0fb3cfac27',\n]);\n\n/** True when `content` is a shipped revision of safeword's lane template. */\nexport function isShippedCucumberTemplateRevision(content: string): boolean {\n const hash = createHash('sha256').update(content).digest('hex');\n return CUCUMBER_TEMPLATE_REVISION_HASHES.has(hash);\n}\n"],"mappings":"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","names":["isGitRepo","lstatSync","nodePath","nodePath","lstatSync","nodePath","nodePath","nodePath","existsSync","readdirSync","nodePath","existsSync","readdirSync","nodePath","nodePath","nodePath","nodePath","nodePath","nodePath","execFileSync","nodePath","execFileSync","nodePath","existsSync","readdirSync","readFileSync","nodePath","detectLanguages","readdirSync","nodePath","existsSync","readFileSync","nodePath","detectLanguages"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/test-plan/resolve.ts"],"sourcesContent":["/**\n * test-plan resolver (ticket BKTTZA).\n *\n * Given a repo root, emit the test/build command for EVERY language present —\n * polyglot, no first-match. Pure except for an injectable `isToolAvailable`\n * probe, so runner selection is unit-testable without real toolchains. Plan-only:\n * each entry carries the command it WOULD run; callers execute.\n *\n * Reaches the consumers (verify/audit/test-runner) via the `safeword project test-plan`\n * CLI — shipped hooks cannot import safeword code, so the CLI is the seam.\n *\n * Manifest discovery is a single tree walk (`indexFilesInTree`) shared by every\n * language resolver, so a complex/deep monorepo costs one traversal, not one per\n * probe.\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport nodePath from 'node:path';\nimport process from 'node:process';\n\nimport { findFileMatchingInTree, indexFilesInTree } from '../utils/fs.js';\nimport { detectPackageManager } from '../utils/install.js';\n\nexport type PlanKind = 'test' | 'build' | 'verify' | 'typecheck' | 'deps' | 'bdd';\nexport type Language = 'javascript' | 'python' | 'go' | 'rust';\n\nexport interface PlanEntry {\n language: Language;\n /** Directory the command runs in (the discovered manifest's directory). */\n cwd: string;\n /** The command string to run. Plan-only — never executed here. */\n command: string;\n /** The detected runner (e.g. pytest, tox, unittest, nextest, cargo, go, pnpm). */\n runner: string;\n /** Whether the runner's toolchain is installed. Unavailable entries are kept (visible), never dropped. */\n available: boolean;\n}\n\nexport interface ResolveOptions {\n kind?: PlanKind;\n /** Injected for tests; defaults to a real `command -v` probe. */\n isToolAvailable?: (tool: string) => boolean;\n}\n\ntype ToolProbe = (tool: string) => boolean;\n/** filename → directory of its shallowest occurrence (from one tree walk). */\ntype ManifestIndex = ReadonlyMap<string, string>;\n\nconst PYTHON_MANIFESTS = ['pyproject.toml', 'requirements.txt', 'setup.py', 'setup.cfg', 'tox.ini'];\n// Per-tool config-marker files — the opt-in signals that gate the python\n// `typecheck` (mypy/pyright) and `bdd` (behave) lanes. Single-sourced here so a\n// new marker lands in the detector, the cwd resolution, AND the tree index with\n// one edit (each name is also read by the matching *Configured detector below).\nconst MYPY_MARKER_FILES = ['mypy.ini', '.mypy.ini'] as const;\nconst PYRIGHT_MARKER_FILES = ['pyrightconfig.json'] as const;\nconst BEHAVE_MARKER_FILES = ['behave.ini', '.behaverc'] as const;\n/** Every manifest probed via the tree (root-only files like package.json/go.work are checked directly). */\nconst TREE_MANIFESTS = new Set<string>([\n ...PYTHON_MANIFESTS,\n 'pytest.ini',\n '.pytest.ini',\n 'uv.lock',\n 'poetry.lock',\n ...MYPY_MARKER_FILES,\n ...PYRIGHT_MARKER_FILES,\n ...BEHAVE_MARKER_FILES,\n 'go.mod',\n 'Cargo.toml',\n]);\n\n/**\n * Kinds each non-Rust resolver opts out of, so a new PlanKind fails safe (the\n * language emits nothing) instead of falling through to a wrong command. `deps`\n * emits an ecosystem-native vulnerability scan for every supported language.\n * `typecheck` emits for JS/TS (`typecheck`\n * script), Python (mypy/pyright when configured), and Rust (clippy, in\n * resolveRust); Go's compiler covers it. `bdd` (Gherkin acceptance) emits for JS\n * (cucumber-js) and Python (behave); Go's godog and Rust's cucumber-rs fold into\n * the native test lane, so those skip it. Frozen sets mirror the manifest-set\n * idiom above and keep the guards uniform.\n */\nconst PYTHON_SKIP_KINDS: ReadonlySet<PlanKind> = new Set<PlanKind>(['build']);\nconst GO_SKIP_KINDS: ReadonlySet<PlanKind> = new Set<PlanKind>(['typecheck', 'bdd']);\n\n/**\n * Parse the `SAFEWORD_FAKE_TOOLS` test seam (same spirit as `SAFEWORD_SKIP_INSTALL`):\n * `all` → everything installed; `only:a,b` → allowlist; `none:a,b` → denylist.\n * Lets the BDD acceptance lane drive the real CLI deterministically without\n * depending on the host's actual toolchains.\n */\nfunction fakeToolProbe(spec: string): (tool: string) => boolean {\n if (spec.startsWith('only:')) {\n const set = parseToolList(spec, 'only:');\n return tool => set.has(tool);\n }\n if (spec.startsWith('none:')) {\n const set = parseToolList(spec, 'none:');\n return tool => !set.has(tool);\n }\n return allToolsAvailable; // 'all' or empty\n}\n\n/** Parse the comma-separated tool list after a `only:` / `none:` prefix. */\nfunction parseToolList(spec: string, prefix: string): Set<string> {\n return new Set(spec.slice(prefix.length).split(',').filter(Boolean));\n}\n\nfunction allToolsAvailable(): boolean {\n return true;\n}\n\nfunction defaultIsToolAvailable(tool: string): boolean {\n const fake = process.env.SAFEWORD_FAKE_TOOLS;\n if (fake !== undefined) return fakeToolProbe(fake)(tool);\n if (process.platform === 'win32') {\n return spawnSync('where.exe', [tool], { stdio: 'ignore' }).status === 0;\n }\n return (\n spawnSync('/bin/sh', ['-c', 'command -v \"$1\"', 'safeword-tool-probe', tool], {\n stdio: 'ignore',\n }).status === 0\n );\n}\n\nfunction entry(\n language: Language,\n cwd: string,\n command: string,\n runner: string,\n available: boolean,\n): PlanEntry {\n return { language, cwd, command, runner, available };\n}\n\nfunction readInstalledPacks(root: string): Set<string> | undefined {\n const configPath = nodePath.join(root, '.safeword', 'config.json');\n if (!existsSync(configPath)) return undefined;\n\n try {\n const parsed = JSON.parse(readFileSync(configPath, 'utf8')) as { installedPacks?: unknown };\n if (!Array.isArray(parsed.installedPacks)) return undefined;\n return new Set(\n parsed.installedPacks.filter((pack): pack is string => typeof pack === 'string'),\n );\n } catch {\n return undefined;\n }\n}\n\nfunction isLanguageEnabled(language: Language, installedPacks: Set<string> | undefined): boolean {\n if (installedPacks === undefined) return true;\n\n if (language === 'javascript') {\n return installedPacks.has('typescript') || installedPacks.has('javascript');\n }\n if (language === 'go') {\n return installedPacks.has('golang') || installedPacks.has('go');\n }\n return installedPacks.has(language);\n}\n\n/** Directory of the first listed manifest present in the index, or root if none. */\nfunction firstDirectory(root: string, index: ManifestIndex, names: readonly string[]): string {\n for (const name of names) {\n const dir = index.get(name);\n if (dir !== undefined) return dir;\n }\n return root;\n}\n\n/** Root package.json scripts, or undefined when absent/unparseable (so a malformed manifest never aborts the plan). */\nfunction readRootScripts(root: string): Record<string, string> | undefined {\n try {\n const pkg = JSON.parse(readFileSync(nodePath.join(root, 'package.json'), 'utf8')) as {\n scripts?: Record<string, string>;\n };\n return pkg.scripts ?? {};\n } catch {\n return undefined;\n }\n}\n\n/**\n * Kinds that map 1:1 to a project's own package.json script: `bdd` → the separate\n * cucumber-js `test:bdd` lane (`npm test` doesn't run it); `typecheck` → the\n * `tsc --noEmit` CI-lint signal a green test run can hide (#436); `build` → the\n * build script. Each emits only when the script exists.\n */\nconst JS_DIRECT_SCRIPT: Partial<Record<PlanKind, string>> = {\n bdd: 'test:bdd',\n typecheck: 'typecheck',\n build: 'build',\n};\n\nfunction resolveJs(\n root: string,\n _index: ManifestIndex,\n kind: PlanKind,\n isAvailable: ToolProbe,\n): PlanEntry | undefined {\n // JS is detected root-only: subdirectory package.json is too common to treat as a project root.\n const scripts = readRootScripts(root);\n if (!scripts) return undefined;\n const pm = detectPackageManager(root);\n if (kind === 'deps') {\n const command = pm === 'yarn' ? 'yarn npm audit' : `${pm} audit`;\n return entry('javascript', root, command, pm, isAvailable(pm));\n }\n const directScript = JS_DIRECT_SCRIPT[kind];\n if (directScript !== undefined) {\n const command = scripts[directScript];\n return command\n ? entry('javascript', root, `${pm} run ${directScript}`, pm, isAvailable(pm))\n : undefined;\n }\n const pickScript = kind === 'verify' ? pickVerifyScript : pickTestScript;\n const script = pickScript(scripts);\n return script ? entry('javascript', root, `${pm} run ${script}`, pm, isAvailable(pm)) : undefined;\n}\n\n/** Returns the first script name in `priority` that exists in `scripts`, or undefined. */\nfunction firstScript(\n scripts: Record<string, string>,\n priority: readonly string[],\n): string | undefined {\n return priority.find(name => Object.hasOwn(scripts, name));\n}\n\n/** Prefer a gate-tuned `test:done` subset, else `test`; undefined when neither exists. */\nfunction pickTestScript(scripts: Record<string, string>): string | undefined {\n return firstScript(scripts, ['test:done', 'test']);\n}\n\n/** For done-gate verification: prefer the authoritative full suite over fast subsets. */\nfunction pickVerifyScript(scripts: Record<string, string>): string | undefined {\n return firstScript(scripts, ['test:ci', 'test', 'test:done']);\n}\n\n/** True when an indexed `file` contains `marker`. */\nfunction configContains(index: ManifestIndex, file: string, marker: string): boolean {\n const dir = index.get(file);\n if (dir === undefined) return false;\n try {\n return readFileSync(nodePath.join(dir, file), 'utf8').includes(marker);\n } catch {\n return false;\n }\n}\n\nfunction pytestConfigured(index: ManifestIndex): boolean {\n if (index.has('pytest.ini') || index.has('.pytest.ini')) return true;\n if (configContains(index, 'pyproject.toml', '[tool.pytest.ini_options]')) return true;\n return configContains(index, 'setup.cfg', '[tool:pytest]');\n}\n\n/**\n * behave is the one Python Gherkin runner that does NOT fold into pytest — it has\n * its own `behave` command. pytest-bdd (the other option) is collected by pytest\n * and already runs in the test lane, so the `bdd` kind gates strictly on behave\n * config to avoid double-running its scenarios.\n *\n * `tox.ini [behave]` is deliberately NOT a signal: when a tox.ini exists, the\n * test/verify lane already runs `tox` (the umbrella runner, which typically drives\n * behave), so keying the bdd lane on it too would double-run the same scenarios.\n */\nfunction behaveConfigured(index: ManifestIndex): boolean {\n if (BEHAVE_MARKER_FILES.some(file => index.has(file))) return true;\n if (configContains(index, 'pyproject.toml', '[tool.behave]')) return true;\n return configContains(index, 'setup.cfg', '[behave]');\n}\n\n/** mypy config markers — the opt-in signal for the python `typecheck` lane. */\nfunction mypyConfigured(index: ManifestIndex): boolean {\n if (MYPY_MARKER_FILES.some(file => index.has(file))) return true;\n if (configContains(index, 'pyproject.toml', '[tool.mypy]')) return true;\n return configContains(index, 'setup.cfg', '[mypy]');\n}\n\n/** pyright config markers — the fallback opt-in signal for the python `typecheck` lane. */\nfunction pyrightConfigured(index: ManifestIndex): boolean {\n if (PYRIGHT_MARKER_FILES.some(file => index.has(file))) return true;\n return configContains(index, 'pyproject.toml', '[tool.pyright]');\n}\n\nfunction isPythonTestFile(filename: string): boolean {\n return (\n filename.endsWith('.py') &&\n (filename.startsWith('test_') ||\n filename.endsWith('_test.py') ||\n filename.endsWith('_tests.py'))\n );\n}\n\n/**\n * Package-manager-aware invocation for a Python tool (pytest, behave, mypy, …).\n * Returns the command plus the binary whose presence gates `available`: the package\n * manager when the project is uv/poetry-locked, else the tool itself.\n */\nfunction pythonInvocation(\n index: ManifestIndex,\n isAvailable: ToolProbe,\n binary: string,\n args = '',\n): { command: string; gate: string } {\n const suffix = args ? ` ${args}` : '';\n if (index.has('uv.lock') && isAvailable('uv'))\n return { command: `uv run ${binary}${suffix}`, gate: 'uv' };\n if (index.has('poetry.lock') && isAvailable('poetry'))\n return { command: `poetry run ${binary}${suffix}`, gate: 'poetry' };\n return { command: `${binary}${suffix}`, gate: binary };\n}\n\n/**\n * Python static type-check lane. Opt-in only: Python is dynamically typed and tests\n * never check types, so this emits a command ONLY when the project configures mypy\n * or pyright — the same shape as the JS `typecheck` script gate (#436).\n */\nfunction resolvePythonTypecheck(\n index: ManifestIndex,\n cwd: string,\n isAvailable: ToolProbe,\n): PlanEntry | undefined {\n if (mypyConfigured(index)) {\n const { command, gate } = pythonInvocation(index, isAvailable, 'mypy', '.');\n return entry('python', cwd, command, 'mypy', isAvailable(gate));\n }\n if (pyrightConfigured(index)) {\n const { command, gate } = pythonInvocation(index, isAvailable, 'pyright');\n return entry('python', cwd, command, 'pyright', isAvailable(gate));\n }\n return undefined;\n}\n\n/** Python standalone-BDD lane — behave only (pytest-bdd already runs in the test lane). */\nfunction resolvePythonBdd(\n index: ManifestIndex,\n cwd: string,\n isAvailable: ToolProbe,\n): PlanEntry | undefined {\n if (!behaveConfigured(index)) return undefined;\n const { command, gate } = pythonInvocation(index, isAvailable, 'behave');\n return entry('python', cwd, command, 'behave', isAvailable(gate));\n}\n\n/** Python unit-test lane (kind: test | verify) — tox, then pytest, then unittest. */\nfunction resolvePythonTest(\n index: ManifestIndex,\n cwd: string,\n isAvailable: ToolProbe,\n): PlanEntry | undefined {\n if (index.has('tox.ini')) return entry('python', cwd, 'tox', 'tox', isAvailable('tox'));\n const hasPythonTests = findFileMatchingInTree(cwd, isPythonTestFile) !== undefined;\n if (pytestConfigured(index) || (hasPythonTests && isAvailable('pytest'))) {\n const { command, gate } = pythonInvocation(index, isAvailable, 'pytest');\n return entry('python', cwd, command, 'pytest', isAvailable(gate));\n }\n if (!hasPythonTests) return undefined;\n // Prefer python3 (the only `python` on macOS/modern distros), fall back to python.\n const pythonBin = isAvailable('python3') ? 'python3' : 'python';\n return entry(\n 'python',\n cwd,\n `${pythonBin} -m unittest discover`,\n 'unittest',\n isAvailable(pythonBin),\n );\n}\n\nfunction resolvePython(\n root: string,\n index: ManifestIndex,\n kind: PlanKind,\n isAvailable: ToolProbe,\n): PlanEntry | undefined {\n if (PYTHON_SKIP_KINDS.has(kind)) return undefined; // build: no standard Python lane\n // typecheck/bdd detect Python via their OWN config markers (mypy/pyright/behave\n // configs are Python-only), so they don't require a packaging manifest — a repo\n // carrying just `mypy.ini` or `behave.ini` still gets its lane, run in the dir the\n // config lives in. The test lane keeps the manifest gate so a non-Python directory\n // never yields a phantom pytest lane.\n if (kind === 'typecheck') {\n const cwd = firstDirectory(root, index, [\n ...MYPY_MARKER_FILES,\n ...PYRIGHT_MARKER_FILES,\n 'pyproject.toml',\n 'setup.cfg',\n ]);\n return resolvePythonTypecheck(index, cwd, isAvailable);\n }\n if (kind === 'bdd') {\n const cwd = firstDirectory(root, index, [\n ...BEHAVE_MARKER_FILES,\n 'pyproject.toml',\n 'setup.cfg',\n ]);\n return resolvePythonBdd(index, cwd, isAvailable);\n }\n if (PYTHON_MANIFESTS.every(manifest => !index.has(manifest))) return undefined;\n if (kind === 'deps') {\n const cwd = firstDirectory(root, index, PYTHON_MANIFESTS);\n // `uv audit` is uv's native dependency audit. Other Python packaging\n // layouts use pip-audit, which is maintained by PyPA. Select from project\n // evidence rather than tool availability so a missing scanner stays a\n // visible skip in the rendered plan instead of a false-green no-op.\n if (index.has('uv.lock')) return entry('python', cwd, 'uv audit', 'uv', isAvailable('uv'));\n if (index.has('requirements.txt'))\n return entry(\n 'python',\n cwd,\n 'pip-audit -r requirements.txt',\n 'pip-audit',\n isAvailable('pip-audit'),\n );\n if (index.has('pyproject.toml'))\n return entry('python', cwd, 'pip-audit .', 'pip-audit', isAvailable('pip-audit'));\n // Legacy setup.py/setup.cfg/tox projects have no project-file input that\n // pip-audit understands. Auditing the active environment is the supported\n // fallback and remains visible if the scanner is unavailable.\n return entry('python', cwd, 'pip-audit', 'pip-audit', isAvailable('pip-audit'));\n }\n return resolvePythonTest(index, firstDirectory(root, index, PYTHON_MANIFESTS), isAvailable);\n}\n\nfunction resolveGo(\n root: string,\n index: ManifestIndex,\n kind: PlanKind,\n isAvailable: ToolProbe,\n): PlanEntry | undefined {\n // Go skips typecheck/bdd (GO_SKIP_KINDS): the compiler is the type checker and\n // godog runs as `go test` subtests.\n if (GO_SKIP_KINDS.has(kind)) return undefined;\n if (!index.has('go.mod')) return undefined;\n if (kind === 'deps') {\n const cwd = existsSync(nodePath.join(root, 'go.work')) ? root : (index.get('go.mod') ?? root);\n // Pin the scanner so this gate is reproducible and does not unexpectedly\n // inherit a new govulncheck release through `@latest`.\n return entry(\n 'go',\n cwd,\n 'go run golang.org/x/vuln/cmd/govulncheck@v1.6.0 ./...',\n 'govulncheck',\n isAvailable('go'),\n );\n }\n const verb = kind === 'build' ? 'build' : 'test';\n // A root go.work tests every workspace module — run the expansion from root.\n // Otherwise run `./...` in the module's own directory (supports nested modules).\n if (existsSync(nodePath.join(root, 'go.work'))) {\n const command = `go ${verb} $(go list -f '{{.Dir}}/...' -m | xargs)`;\n return entry('go', root, command, 'go', isAvailable('go'));\n }\n const cwd = index.get('go.mod') ?? root;\n return entry('go', cwd, `go ${verb} ./...`, 'go', isAvailable('go'));\n}\n\nfunction resolveRust(\n root: string,\n index: ManifestIndex,\n kind: PlanKind,\n isAvailable: ToolProbe,\n): PlanEntry | undefined {\n // bdd folds into the native Rust lane: cucumber-rs runs under `cargo test` (a\n // harness=false test target), so no separate lane. (typecheck IS handled below —\n // clippy is the strict CI-lint gate, #436 — and deps runs cargo-deny.)\n if (kind === 'bdd') return undefined;\n if (!index.has('Cargo.toml')) return undefined;\n const cwd = index.get('Cargo.toml') ?? root;\n if (kind === 'deps')\n // Supply-chain gate: `cargo deny check advisories` scans the RustSec DB (the\n // cargo-audit replacement) — the universal, ~zero-false-positive security\n // signal. Scoped to advisories on purpose: this runs at a BLOCKING done-gate\n // over the whole dep tree, so licenses/sources (policy checks that false-red\n // on missing license metadata or intentional git deps) stay configured in\n // deny.toml but opt-in, not a default that blocks unrelated changes.\n // Visible-but-unavailable when cargo-deny is absent (surfaces the install).\n return entry(\n 'rust',\n cwd,\n 'cargo deny check advisories',\n 'cargo-deny',\n isAvailable('cargo-deny'),\n );\n if (kind === 'typecheck')\n // The typecheck kind is the strict CI-lint signal a green targeted-test run\n // can hide (#436). Clippy is a rustc driver — it wraps `cargo check`, so this\n // subsumes a plain compile check and adds lint enforcement. `--all-targets\n // --all-features` reaches test/bench targets and feature-gated code the\n // per-file `clippy -p <pkg> --fix` hook cannot see; `-D warnings` makes it a\n // gate. Reads the project's own Cargo.toml `[lints]` + clippy.toml.\n return entry(\n 'rust',\n cwd,\n 'cargo clippy --workspace --all-targets --all-features -- -D warnings',\n 'clippy',\n isAvailable('cargo-clippy'),\n );\n if (kind === 'build')\n return entry('rust', cwd, 'cargo build --workspace', 'cargo', isAvailable('cargo'));\n if (isAvailable('cargo-nextest'))\n // nextest doesn't run doctests — append `cargo test --doc` so they aren't silently skipped.\n return entry(\n 'rust',\n cwd,\n 'cargo nextest run --workspace && cargo test --doc',\n 'nextest',\n isAvailable('cargo'),\n );\n return entry('rust', cwd, 'cargo test --workspace', 'cargo', isAvailable('cargo'));\n}\n\n/**\n * Resolve the test (or build) plan for a repo: one entry per detected language.\n * Languages whose toolchain is missing are still listed (`available:false`).\n */\nexport function resolveTestPlan(root: string, options: ResolveOptions = {}): PlanEntry[] {\n const kind = options.kind ?? 'test';\n const isAvailable = options.isToolAvailable ?? defaultIsToolAvailable;\n const index = indexFilesInTree(root, TREE_MANIFESTS);\n const installedPacks = readInstalledPacks(root);\n const resolvers = [resolveJs, resolvePython, resolveGo, resolveRust];\n return resolvers\n .map(resolve => resolve(root, index, kind, isAvailable))\n .filter(\n (planEntry): planEntry is PlanEntry =>\n planEntry !== undefined && isLanguageEnabled(planEntry.language, installedPacks),\n );\n}\n"],"mappings":"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","names":["cwd"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/fs.ts"],"sourcesContent":["/**\n * File system utilities for CLI operations\n */\n\nimport {\n chmodSync,\n type Dirent,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n realpathSync,\n rmdirSync,\n rmSync,\n statSync,\n writeFileSync,\n} from 'node:fs';\nimport nodePath from 'node:path';\n\n// Get the directory of this module (for locating templates)\nconst __dirname = import.meta.dirname;\n\n/**\n * Get path to bundled templates directory.\n * Works in both development (src/) and production (dist/) contexts.\n *\n * Note: We check for SAFEWORD.md to distinguish from src/templates/ which\n * contains TypeScript source files (config.ts, content.ts).\n *\n * Path resolution (bundled with tsup):\n * - From dist/chunk-*.js: __dirname = packages/cli/dist/ → ../templates\n */\nexport function getTemplatesDirectory(): string {\n const knownTemplateFile = 'SAFEWORD.md';\n\n // Try different relative paths - the bundled code ends up in dist/ directly (flat)\n // while source is in src/utils/\n const candidates = [\n nodePath.join(__dirname, '..', 'templates'), // From dist/ (flat bundled)\n nodePath.join(__dirname, '..', '..', 'templates'), // From src/utils/ or dist/utils/\n nodePath.join(__dirname, 'templates'), // Direct sibling (unlikely but safe)\n ];\n\n for (const candidate of candidates) {\n if (existsSync(nodePath.join(candidate, knownTemplateFile))) {\n return candidate;\n }\n }\n\n throw new Error('Templates directory not found');\n}\n\n/**\n * Check if a path exists\n * @param path\n */\n/** True when `path` exists and is a directory (a file is not). */\nexport function isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Resolve an existing directory to a normalized canonical path. */\nexport function canonicalDirectory(path: unknown): string | undefined {\n if (typeof path !== 'string' || path.trim() === '') return undefined;\n try {\n if (!statSync(path).isDirectory()) return undefined;\n return nodePath.normalize(realpathSync(path));\n } catch {\n return undefined;\n }\n}\n\nexport function exists(path: string): boolean {\n return existsSync(path);\n}\n\n/**\n * Directories to exclude when scanning subdirectories for language manifests.\n * These contain vendored/generated files that would cause false positives.\n */\nconst SUBDIRECTORY_EXCLUDE = new Set([\n 'node_modules',\n '.git',\n '.safeword',\n 'vendor',\n // Go toolchains ignore testdata/ entirely; repos keep fixture go.mod files there\n // (with real module directives) that must not surface as discovered packages.\n 'testdata',\n 'dist',\n 'build',\n 'target',\n 'coverage',\n 'dbt_packages',\n 'out',\n '.next',\n '.nuxt',\n '.output',\n '__pycache__',\n '.venv',\n 'venv',\n]);\n\n/**\n * Check if a file exists anywhere in the project tree.\n * Recursively scans subdirectories, skipping excluded directories.\n *\n * @param cwd - Project root directory\n * @param filename - File to search for (e.g., 'pyproject.toml')\n * @returns true if found anywhere in the project tree\n */\nexport function existsInTree(cwd: string, filename: string): boolean {\n return findInTree(cwd, filename) !== undefined;\n}\n\n/**\n * Find a file anywhere in the project tree.\n * Recursively scans subdirectories, skipping excluded directories.\n * Root is checked first; deeper matches use depth-first traversal.\n *\n * @param cwd - Project root directory\n * @param filename - File to search for (e.g., 'pyproject.toml')\n * @param maxDepth - Maximum directory depth to scan (default: 10)\n * @returns Directory path where file was found, or undefined\n */\nexport function findInTree(cwd: string, filename: string, maxDepth = 10): string | undefined {\n // Check root first\n if (existsSync(nodePath.join(cwd, filename))) {\n return cwd;\n }\n\n return scanTreeForFile(cwd, filename, 1, maxDepth);\n}\n\n/** Whether a dirent is a subdirectory safe to recurse into (not hidden, not excluded). */\nfunction isScannableSubdirectory(entry: Dirent): boolean {\n return (\n entry.isDirectory() && !entry.name.startsWith('.') && !SUBDIRECTORY_EXCLUDE.has(entry.name)\n );\n}\n\n/** Return scannable subdirectory paths (excludes hidden dirs and SUBDIRECTORY_EXCLUDE). */\nfunction getScannableSubdirectories(directory: string): string[] {\n try {\n return readdirSync(directory, { withFileTypes: true })\n .filter(entry => isScannableSubdirectory(entry))\n .map(entry => nodePath.join(directory, entry.name));\n } catch {\n return [];\n }\n}\n\nfunction scanTreeForFile(\n directory: string,\n filename: string,\n depth: number,\n maxDepth: number,\n): string | undefined {\n if (depth > maxDepth) return undefined;\n\n const subdirectories = getScannableSubdirectories(directory);\n\n // Check all children at this level first\n for (const subdirectory of subdirectories) {\n if (existsSync(nodePath.join(subdirectory, filename))) {\n return subdirectory;\n }\n }\n\n // Then recurse deeper\n for (const subdirectory of subdirectories) {\n const result = scanTreeForFile(subdirectory, filename, depth + 1, maxDepth);\n if (result !== undefined) return result;\n }\n\n return undefined;\n}\n\n/**\n * Find the first directory (root first, then descending) that contains a file\n * whose name matches `predicate`. Skips hidden dirs + SUBDIRECTORY_EXCLUDE. The\n * predicate analogue of {@link findInTree} — share this instead of re-walking\n * the tree (and re-declaring the exclude set) in callers.\n * @param cwd - Project root directory\n * @param predicate - Matches a filename (e.g. a Python test-file name)\n * @param maxDepth - Maximum directory depth to scan (default: 10)\n * @returns Directory containing a match, or undefined\n */\nexport function findFileMatchingInTree(\n cwd: string,\n predicate: (filename: string) => boolean,\n maxDepth = 10,\n): string | undefined {\n return scanTreeForMatch(cwd, predicate, 0, maxDepth);\n}\n\nfunction scanTreeForMatch(\n directory: string,\n predicate: (filename: string) => boolean,\n depth: number,\n maxDepth: number,\n): string | undefined {\n if (depth > maxDepth) return undefined;\n\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return undefined;\n }\n\n if (entries.some(entry => entry.isFile() && predicate(entry.name))) {\n return directory;\n }\n\n for (const entry of entries) {\n if (!isScannableSubdirectory(entry)) continue;\n\n const result = scanTreeForMatch(\n nodePath.join(directory, entry.name),\n predicate,\n depth + 1,\n maxDepth,\n );\n if (result !== undefined) return result;\n }\n\n return undefined;\n}\n\n/**\n * Index multiple files in a single tree walk — the multi-file analogue of\n * `findInTree`. Returns a Map from each requested filename to the directory of\n * its **shallowest** occurrence (root-first, level-order — same ordering as\n * `findInTree`), omitting names not found. One traversal regardless of how many\n * filenames are requested, so callers that probe many manifests stay O(one walk)\n * even on large/deep monorepos. Stops early once every name is located.\n */\nexport function indexFilesInTree(\n cwd: string,\n filenames: Iterable<string>,\n maxDepth = 10,\n): Map<string, string> {\n const wanted = [...filenames];\n const found = new Map<string, string>();\n // Cursor instead of Array.shift() so the BFS stays O(dirs), not O(dirs²) on\n // wide/deep monorepos — the very repos this single-walk index exists to serve.\n const queue: { directory: string; depth: number }[] = [{ directory: cwd, depth: 0 }];\n\n let head = 0;\n while (head < queue.length) {\n const item = queue[head];\n head += 1;\n if (item === undefined) break;\n for (const name of wanted) {\n if (!found.has(name) && existsSync(nodePath.join(item.directory, name))) {\n found.set(name, item.directory);\n }\n }\n if (found.size === wanted.length) break;\n if (item.depth >= maxDepth) continue;\n for (const subdirectory of getScannableSubdirectories(item.directory)) {\n queue.push({ directory: subdirectory, depth: item.depth + 1 });\n }\n }\n return found;\n}\n\n/**\n * Every directory (root included) that DIRECTLY contains `filename`, in root-first\n * BFS order, skipping hidden dirs and SUBDIRECTORY_EXCLUDE. The collect-all analogue\n * of {@link findInTree} (which stops at the first match) — used to enumerate orphan\n * language manifests (a `go.mod` / `pyproject.toml` / `Cargo.toml` that is not enrolled\n * in any declared workspace) across a polyglot monorepo in one bounded walk.\n */\nexport function findAllInTree(cwd: string, filename: string, maxDepth = 10): string[] {\n const found: string[] = [];\n // Cursor-based BFS (not Array.shift()) so the walk stays O(dirs) on wide/deep trees,\n // mirroring indexFilesInTree.\n const queue: { directory: string; depth: number }[] = [{ directory: cwd, depth: 0 }];\n let head = 0;\n while (head < queue.length) {\n const item = queue[head];\n head += 1;\n if (item === undefined) break;\n if (existsSync(nodePath.join(item.directory, filename))) found.push(item.directory);\n if (item.depth >= maxDepth) continue;\n for (const subdirectory of getScannableSubdirectories(item.directory)) {\n queue.push({ directory: subdirectory, depth: item.depth + 1 });\n }\n }\n return found;\n}\n\n/**\n * Create directory recursively\n * @param path\n */\nexport function ensureDirectory(path: string): void {\n if (!existsSync(path)) {\n mkdirSync(path, { recursive: true });\n }\n}\n\n/**\n * Read file as string\n * @param path\n */\nexport function readFile(path: string): string {\n return readFileSync(path, 'utf8');\n}\n\n/**\n * Read file as string, return null if not exists\n * @param path\n */\nexport function readFileSafe(path: string): string | undefined {\n if (!existsSync(path)) return undefined;\n return readFileSync(path, 'utf8');\n}\n\n/**\n * Write file, creating parent directories if needed\n * @param path\n * @param content\n */\nexport function writeFile(path: string, content: string): void {\n ensureDirectory(nodePath.dirname(path));\n writeFileSync(path, content);\n}\n\n/**\n * Remove file or directory recursively\n * @param path\n */\nexport function remove(path: string): void {\n if (existsSync(path)) {\n rmSync(path, { recursive: true, force: true });\n }\n}\n\n/**\n * Remove directory only if empty, returns true if removed\n * @param path\n */\nexport function removeIfEmpty(path: string): boolean {\n if (!existsSync(path)) return false;\n try {\n rmdirSync(path); // Non-recursive, throws if not empty\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Make all shell scripts in a directory executable\n * @param dirPath\n */\nexport function makeScriptsExecutable(dirPath: string): void {\n if (!existsSync(dirPath)) return;\n for (const file of readdirSync(dirPath)) {\n if (file.endsWith('.sh')) {\n chmodSync(nodePath.join(dirPath, file), 0o755);\n }\n }\n}\n\n/**\n * Read and parse a JSON file, best-effort: returns `undefined` when no JSON\n * value can be obtained from the path — whether it is absent, empty, unparseable\n * (e.g. JSONC comments or malformed), or unreadable as a file at all. The read is\n * inside the `try` so a directory at the path (`EISDIR`) or a permission error\n * (`EACCES`) degrades to `undefined` like a parse failure, instead of throwing\n * uncaught and crashing callers (e.g. reconcile's jsonMerge). All callers already\n * treat the result as `T | undefined`.\n * @param path\n */\nexport function readJson(path: string): unknown {\n try {\n const content = readFileSafe(path);\n if (!content) return undefined;\n return JSON.parse(content) as unknown;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Write JSON file with formatting\n * @param path\n * @param data\n */\nexport function writeJson(path: string, data: unknown): void {\n writeFile(path, `${JSON.stringify(data, undefined, 2)}\\n`);\n}\n"],"mappings":"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","names":[]}
|