code-foundry 1.39.1 → 1.39.2

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.
@@ -1,4 +1,13 @@
1
1
  #!/usr/bin/env sh
2
+ # Generated by Code Foundry — see src/commands/sync.mjs.
2
3
  set -eu
3
4
 
5
+ if [ -z "$(git diff --cached --name-only)" ]; then
6
+ exit 0
7
+ fi
8
+
9
+ if command -v npx >/dev/null 2>&1 && npx --yes code-foundry@__VERSION__ pre-commit; then
10
+ exit 0
11
+ fi
12
+
4
13
  git diff --cached --check
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.39.2](https://github.com/0xPlayerOne/code-foundry/compare/v1.39.1...v1.39.2) (2026-10-01)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **hooks:** delegate the generated pre-commit hook to the runtime ([#684](https://github.com/0xPlayerOne/code-foundry/issues/684)) ([5d9a571](https://github.com/0xPlayerOne/code-foundry/commit/5d9a5714aeff0d48397e3be51b22e08395bfb5ce))
9
+
3
10
  ## [1.39.1](https://github.com/0xPlayerOne/code-foundry/compare/v1.39.0...v1.39.1) (2026-10-01)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-foundry",
3
- "version": "1.39.1",
3
+ "version": "1.39.2",
4
4
  "description": "A fast, language-aware repository factory for agent-ready workflows, testing, security, and release automation.",
5
5
  "homepage": "https://github.com/0xPlayerOne/code-foundry#readme",
6
6
  "bugs": {
package/src/cli.mjs CHANGED
@@ -271,6 +271,16 @@ async function main() {
271
271
  } catch (error) {
272
272
  fail(error instanceof Error ? error.message : String(error))
273
273
  }
274
+ } else if (command === 'pre-commit') {
275
+ // The generated `.githooks/pre-commit` calls this. Without a CLI entry
276
+ // point the language-aware gate in runtime-core is unreachable from a hook.
277
+ try {
278
+ process.chdir(target)
279
+ const { preCommit } = await import('./runtime-core.mjs')
280
+ preCommit()
281
+ } catch (error) {
282
+ fail(error instanceof Error ? error.message : String(error))
283
+ }
274
284
  } else fail(`unknown command: ${command}`)
275
285
  }
276
286
 
@@ -40,6 +40,20 @@ export function doctor(root, options = {}) {
40
40
  }
41
41
  const hooks = git(target, ['config', '--get', 'core.hooksPath'])
42
42
  if (hooks !== '.githooks') warn('Git hooks are not enabled; run `npx code-foundry init`')
43
+ // A correctly enabled hook that enforces nothing is the failure this check
44
+ // could not see: the generated template used to be the whitespace guard
45
+ // alone while the real gate sat unused in the runtime. Reading only the
46
+ // hooksPath reports success for a hook that does nothing.
47
+ if (hooks === '.githooks') {
48
+ const hookFile = join(target, '.githooks/pre-commit')
49
+ if (!existsSync(hookFile)) warn('No .githooks/pre-commit found; run `npx code-foundry sync`')
50
+ else if (
51
+ !/code-foundry[@\w./-]*\s+(?:--yes\s+)?pre-commit/.test(readFileSync(hookFile, 'utf8'))
52
+ )
53
+ warn(
54
+ '.githooks/pre-commit does not delegate to the Code Foundry gate; run `npx code-foundry sync`'
55
+ )
56
+ }
43
57
  if (
44
58
  ['rust', 'python', 'solidity'].some((language) =>
45
59
  profile.languages.split(',').includes(language)
@@ -260,6 +260,9 @@ function synchronize(options) {
260
260
  if (file === 'LICENSE' && license !== 'preserve' && license !== 'none') continue
261
261
  if (file === '.github/CODEOWNERS' && existsSync(destination)) continue
262
262
  let content = readFileSync(sourceFile)
263
+ if (file === '.githooks/pre-commit') {
264
+ content = Buffer.from(renderHook(sourceFile, source))
265
+ }
263
266
  if (file === 'release-please-config.json') {
264
267
  content = Buffer.from(renderReleaseConfig(target, sourceFile))
265
268
  }
@@ -586,6 +589,20 @@ function renderReleaseConfig(target, sourceFile) {
586
589
  return `${JSON.stringify(mergeReleaseConfig(target, sourceFile), null, 2)}\n`
587
590
  }
588
591
 
592
+ /**
593
+ * The hook runs `npx code-foundry@<version>` on every commit, so the version
594
+ * is substituted at generation time rather than resolved at run time. A
595
+ * mutable dist-tag there would let any publish under that tag run code on
596
+ * every developer's machine at commit time; pinning means the gate a
597
+ * repository received is the gate that generated it.
598
+ *
599
+ * @param {string} sourceFile the template path
600
+ * @param {string} sourceRoot the package root that carries package.json
601
+ */
602
+ function renderHook(sourceFile, sourceRoot) {
603
+ return readFileSync(sourceFile, 'utf8').replaceAll('__VERSION__', readPackageVersion(sourceRoot))
604
+ }
605
+
589
606
  /** @param {string} file @param {string} languages @param {string} features @param {Record<string, string>} config */
590
607
  function shouldInclude(file, languages, features, config) {
591
608
  if (file === 'ruff.toml') return includesValue(languages, 'python')
@@ -7,9 +7,11 @@ import {
7
7
  mkdirSync,
8
8
  readdirSync,
9
9
  readFileSync,
10
+ realpathSync,
10
11
  writeFileSync,
11
12
  } from 'node:fs'
12
13
  import { resolve } from 'node:path'
14
+ import { fileURLToPath } from 'node:url'
13
15
  import { spawnSync } from 'node:child_process'
14
16
  import { detectPackageManager, resolveProfile } from './lib/profile.mjs'
15
17
  import { configured, readConfig } from './lib/config.mjs'
@@ -1165,7 +1167,15 @@ function printProfile(command) {
1165
1167
  writeOutput('npm_publish', config.npm_publish ?? 'false')
1166
1168
  }
1167
1169
 
1168
- function preCommit() {
1170
+ /**
1171
+ * The local commit gate.
1172
+ *
1173
+ * Exported so the generated `.githooks/pre-commit` can reach it: the hook is
1174
+ * what turns this into an actual gate, and it used to run the whitespace
1175
+ * check alone. The generated hook is the consumer-visible contract, so keep
1176
+ * the two in step.
1177
+ */
1178
+ export function preCommit() {
1169
1179
  const changed = capture('git', ['diff', '--cached', '--name-only'])
1170
1180
  if (!changed) return
1171
1181
  const check = spawnSync('git', ['diff', '--cached', '--check'], { cwd: root, stdio: 'inherit' })
@@ -1173,6 +1183,10 @@ function preCommit() {
1173
1183
  if (/\.(js|jsx|ts|tsx|json|md|mdx|yml|yaml)$/.test(changed)) {
1174
1184
  ci('format')
1175
1185
  ci('lint')
1186
+ // Type errors are the fastest check that still needs a full toolchain, and
1187
+ // the most common reason a green-looking commit fails CI later. Run it
1188
+ // here while the dependency graph is warm.
1189
+ ci('type_check')
1176
1190
  }
1177
1191
  if (/\.rs$|(^|\/)Cargo\.toml$/.test(changed)) {
1178
1192
  run('cargo', ['fmt', '--check'])
@@ -1184,16 +1198,34 @@ function preCommit() {
1184
1198
  }
1185
1199
  }
1186
1200
 
1187
- const [area, task, ecosystem] = process.argv.slice(2)
1188
- try {
1189
- if (area === 'ci') ci(task)
1190
- else if (area === 'security') security(task, ecosystem)
1191
- else if (area === 'codeql') codeql()
1192
- else if (area === 'profile') printProfile(task)
1193
- else if (area === 'validation') validation(task)
1194
- else if (area === 'pre-commit') preCommit()
1195
- else throw new Error(`Unknown runtime command: ${area || '(missing)'}`)
1196
- } catch (error) {
1197
- console.error(error instanceof Error ? error.message : String(error))
1198
- process.exitCode = 1
1201
+ // Only dispatch when this module is the process entry point. The CLI imports
1202
+ // `preCommit` from here, and an unguarded dispatch would read argv and run the
1203
+ // gate a second time on import.
1204
+ const invokedDirectly =
1205
+ process.argv[1] !== undefined &&
1206
+ realpathSafe(process.argv[1]) === realpathSafe(fileURLToPath(import.meta.url))
1207
+
1208
+ /** @param {string} p @returns {string} */
1209
+ function realpathSafe(p) {
1210
+ try {
1211
+ return realpathSync(p)
1212
+ } catch {
1213
+ return p
1214
+ }
1215
+ }
1216
+
1217
+ if (invokedDirectly) {
1218
+ const [area, task, ecosystem] = process.argv.slice(2)
1219
+ try {
1220
+ if (area === 'ci') ci(task)
1221
+ else if (area === 'security') security(task, ecosystem)
1222
+ else if (area === 'codeql') codeql()
1223
+ else if (area === 'profile') printProfile(task)
1224
+ else if (area === 'validation') validation(task)
1225
+ else if (area === 'pre-commit') preCommit()
1226
+ else throw new Error(`Unknown runtime command: ${area || '(missing)'}`)
1227
+ } catch (error) {
1228
+ console.error(error instanceof Error ? error.message : String(error))
1229
+ process.exitCode = 1
1230
+ }
1199
1231
  }
@@ -72,6 +72,9 @@ pnpm-debug.log*
72
72
  .fleet/
73
73
  .opencode/
74
74
  .kluster/
75
+ .zcode/
76
+ .kiro/
77
+ .goose/
75
78
  .direnv/
76
79
  .envrc
77
80
  .vercel/