NORTHSET

Proof-of-Pass Receipt — M-012

CONTRIBUTOR SELF-RUN — NOT MAINTAINER VERIFICATION

Receipt ID
M-012
Run start
2026-07-12T22:06:14.381Z
Run finish
2026-07-12T22:06:51.093Z

Project

nodejs/doc-kit

Work

Consideration: detect `doc-kit.config.mjs` automatically · PR #901

Verification execution

runtime: northset-oss executor v0
human operator: aeziz

Code

base
11d012a9956fc778a024a37488f0783e4b1cf90f
recorded patch commit
a0bdd2de6a81b60e2f8a607a40d39b4fa0b22751
declared metadata; not execution-bound
patch diff SHA-256
sha256:524fa5413c71090d9b4d4ad8dfd20f877f163551943875f83db2fbde9903beb3
bound to executed patch bytes

Environment

image reference
node:22-bookworm
repository digest
node@sha256:a25c9934ff6382cd4f08b6bc26c82bf4ea69b1e6f8dabfb2ead457374127c365
network
phaseA:bridge,phaseB:none

Declared checks

Execution summary
1/1 declared command returned exit 0 in the recorded environment

  1. node --test --experimental-test-module-mocks src/utils/configuration/__tests__/index.test.mjs

    exit 0 · 0.7s

unclassified executor time (derived residual) 36s

run wall (derived from recorded timestamps) 36.7s

PASS — 1/1 declared command

Every command listed returned exit 0 in the declared environment. Only the listed commands are in scope. Unlisted test, lint, typecheck, build, coverage, compiler, full-suite, and CI gates are not implied or recorded.

Public scope interpretation

The declared network-off check runs only the configuration unit test (src/utils/configuration/__tests__/index.test.mjs) on node:22 after a disclosed online npm ci. It does not run the full test suite, eslint, prettier, or the TypeScript/JSDoc type-check gates that upstream CI also runs.

Record details

payment
none · not merge-contingent
redactions
none recorded
Bundle contents digest
sha256:2c6b24a0e00782c386b6faf42945791584006b7e510c63b337acec7d621ef891
Signed asset SHA-256
sha256:653a46b74e428acb75738ae1e7b8a1b2b66cb36933087927538a971032561cdf
Signed provenance recorded
verified 2026-07-14T14:21:48Z

NOT INCLUDED

  • Does not prove code quality
  • Does not prove security
  • Contributor self-run record of Northset's own contribution; not the maintainer's verification.
  • The declared network-off check runs only the configuration unit test (src/utils/configuration/__tests__/index.test.mjs) on node:22 after a disclosed online npm ci. It does not run the full test suite, eslint, prettier, or the TypeScript/JSDoc type-check gates that upstream CI also runs.

Signed bundle

Download signed bundle

Verify this receipt

gh attestation verify run-record-M-012.tar.gz --repo northset-oss/verification-pilot --signer-workflow northset-oss/verification-pilot/.github/workflows/attest-bundle.yml

Attestation confirms that Northset's signing workflow produced this exact bundle. The signer does not witness the recorded run, and verification does not turn it into maintainer verification.

QR → receipt page
Northset contributed this fix and ran its declared checks. Contributor self-run. Not maintainer verification.

Evidence of what ran — not a verdict that the code is good.

SELF-FUNDED FIELD-TESTING.

Committed patch.diff
diff --git a/.changeset/quiet-dingos-detect.md b/.changeset/quiet-dingos-detect.md
new file mode 100644
index 0000000..fb55278
--- /dev/null
+++ b/.changeset/quiet-dingos-detect.md
@@ -0,0 +1,5 @@
+---
+'@node-core/doc-kit': patch
+---
+
+Automatically load `doc-kit.config.mjs` from the current working directory.
diff --git a/src/utils/configuration/__tests__/index.test.mjs b/src/utils/configuration/__tests__/index.test.mjs
index ac7bf50..8ce318f 100644
--- a/src/utils/configuration/__tests__/index.test.mjs
+++ b/src/utils/configuration/__tests__/index.test.mjs
@@ -1,10 +1,13 @@
 import assert from 'node:assert';
+import * as nodeFs from 'node:fs';
+import { join } from 'node:path';
 import { describe, it, mock, beforeEach } from 'node:test';
 
 // Mock dependencies
 const mockParseChangelog = mock.fn(async changelog => [changelog]);
 const mockParseIndex = mock.fn(async index => [index]);
 const mockImportFromURL = mock.fn(async () => ({}));
+const mockExistsSync = mock.fn(() => false);
 
 const createMockConfig = (overrides = {}) => ({
   global: {},
@@ -31,6 +34,9 @@ mock.module('../../../parsers/markdown.mjs', {
 mock.module('../../loaders.mjs', {
   namedExports: { importFromURL: mockImportFromURL },
 });
+mock.module('node:fs', {
+  namedExports: { ...nodeFs, existsSync: mockExistsSync },
+});
 
 const {
   assertRunnableOptions,
@@ -43,9 +49,12 @@ const {
 
 // Helper to reset all mocks
 const resetAllMocks = () => {
-  [mockParseChangelog, mockParseIndex, mockImportFromURL].forEach(m =>
-    m.mock.resetCalls()
-  );
+  [
+    mockParseChangelog,
+    mockParseIndex,
+    mockImportFromURL,
+    mockExistsSync,
+  ].forEach(m => m.mock.resetCalls());
 };
 
 // Helper to count specific function calls
@@ -149,6 +158,47 @@ describe('config.mjs', () => {
   });
 
   describe('createRunConfiguration', () => {
+    it('should auto-detect a config file in the current directory', async () => {
+      const defaultConfigFile = join(process.cwd(), 'doc-kit.config.mjs');
+      const mockConfig = createMockConfig({
+        global: { input: 'auto-detected-src/' },
+      });
+      mockExistsSync.mock.mockImplementationOnce(() => true);
+      mockImportFromURL.mock.mockImplementationOnce(async () => mockConfig);
+
+      const config = await createRunConfiguration({});
+
+      assert.strictEqual(config.global.input, 'auto-detected-src/');
+      assert.strictEqual(mockExistsSync.mock.calls.length, 1);
+      assert.strictEqual(
+        mockExistsSync.mock.calls[0].arguments[0],
+        defaultConfigFile
+      );
+      assert.strictEqual(mockImportFromURL.mock.calls.length, 1);
+      assert.strictEqual(
+        mockImportFromURL.mock.calls[0].arguments[0],
+        defaultConfigFile
+      );
+    });
+
+    it('should prefer an explicit config file', async () => {
+      mockImportFromURL.mock.mockImplementationOnce(async () =>
+        createMockConfig({ global: { input: 'explicit-src/' } })
+      );
+
+      const config = await createRunConfiguration({
+        configFile: 'explicit-config.mjs',
+      });
+
+      assert.strictEqual(config.global.input, 'explicit-src/');
+      assert.strictEqual(mockExistsSync.mock.calls.length, 0);
+      assert.strictEqual(mockImportFromURL.mock.calls.length, 1);
+      assert.strictEqual(
+        mockImportFromURL.mock.calls[0].arguments[0],
+        'explicit-config.mjs'
+      );
+    });
+
     it('should merge config sources in correct order', async () => {
       mockImportFromURL.mock.mockImplementationOnce(async () =>
         createMockConfig({ global: { input: 'custom-src/' } })
@@ -204,7 +254,7 @@ describe('config.mjs', () => {
       assert.strictEqual(config.chunkSize, 1);
     });
 
-    it('should work without config file', async () => {
+    it('should use an empty config when no config file is present', async () => {
       const config = await createRunConfiguration({
         version: '20.0.0',
         threads: 4,
@@ -212,6 +262,11 @@ describe('config.mjs', () => {
 
       assert.ok(config);
       assert.strictEqual(config.threads, 4);
+      assert.strictEqual(mockExistsSync.mock.calls.length, 1);
+      assert.strictEqual(
+        mockExistsSync.mock.calls[0].arguments[0],
+        join(process.cwd(), 'doc-kit.config.mjs')
+      );
       assert.strictEqual(mockImportFromURL.mock.calls.length, 0);
     });
 
diff --git a/src/utils/configuration/index.mjs b/src/utils/configuration/index.mjs
index a31bc35..9f5536b 100644
--- a/src/utils/configuration/index.mjs
+++ b/src/utils/configuration/index.mjs
@@ -1,4 +1,6 @@
+import { existsSync } from 'node:fs';
 import { cpus } from 'node:os';
+import { join } from 'node:path';
 import { isMainThread } from 'node:worker_threads';
 
 import { coerce } from 'semver';
@@ -123,7 +125,8 @@ export const assertRunnableOptions = config => {
 };
 
 /**
- * Creates a complete run configuration by merging config file, user options, and defaults.
+ * Creates a complete run configuration by merging an explicit or auto-detected
+ * config file, user options, and defaults.
  * Processes and validates configuration values including version coercion, changelog parsing,
  * and constraint enforcement for threads and chunk size.
  *
@@ -131,7 +134,11 @@ export const assertRunnableOptions = config => {
  * @returns {Promise<import('./types').Configuration>} The configuration
  */
 export const createRunConfiguration = async options => {
-  const config = await loadConfigFile(options.configFile);
+  const defaultConfigFile = join(process.cwd(), 'doc-kit.config.mjs');
+  const configFile =
+    options.configFile ??
+    (existsSync(defaultConfigFile) ? defaultConfigFile : undefined);
+  const config = await loadConfigFile(configFile);
   config.target &&= enforceArray(config.target);
 
   // Merge with defaults
Redacted stdout
=== cmd 1: node --test --experimental-test-module-mocks src/utils/configuration/__tests__/index.test.mjs ===
TAP version 13
# (node:16) ExperimentalWarning: Module mocking is an experimental feature and might change at any time
# (Use `node --trace-warnings ...` to show where the warning was created)
# Subtest: config.mjs
    # Subtest: loadConfigFile
        # Subtest: should load config from file path
        ok 1 - should load config from file path
          ---
          duration_ms: 0.750042
          type: 'test'
          ...
        # Subtest: should return empty object for falsy paths
        ok 2 - should return empty object for falsy paths
          ---
          duration_ms: 0.4375
          type: 'test'
          ...
        1..2
    ok 1 - loadConfigFile
      ---
      duration_ms: 1.599792
      type: 'suite'
      ...
    # Subtest: createConfigFromCLIOptions
        # Subtest: should convert CLI options to config structure
        ok 1 - should convert CLI options to config structure
          ---
          duration_ms: 0.916875
          type: 'test'
          ...
        # Subtest: should handle empty options
        ok 2 - should handle empty options
          ---
          duration_ms: 1.205833
          type: 'test'
          ...
        1..2
    ok 2 - createConfigFromCLIOptions
      ---
      duration_ms: 2.334
      type: 'suite'
      ...
    # Subtest: assertRunnableOptions
        # Subtest: should throw when target is missing
        ok 1 - should throw when target is missing
          ---
          duration_ms: 0.567875
          type: 'test'
          ...
        # Subtest: should throw when input is missing
        ok 2 - should throw when input is missing
          ---
          duration_ms: 0.137959
          type: 'test'
          ...
        # Subtest: should not throw when both target and input are provided
        ok 3 - should not throw when both target and input are provided
          ---
          duration_ms: 0.314917
          type: 'test'
          ...
        1..3
    ok 3 - assertRunnableOptions
      ---
      duration_ms: 1.220542
      type: 'suite'
      ...
    # Subtest: createRunConfiguration
        # Subtest: should auto-detect a config file in the current directory
        ok 1 - should auto-detect a config file in the current directory
          ---
          duration_ms: 1.383042
          type: 'test'
          ...
        # Subtest: should prefer an explicit config file
        ok 2 - should prefer an explicit config file
          ---
          duration_ms: 0.285959
          type: 'test'
          ...
        # Subtest: should merge config sources in correct order
        ok 3 - should merge config sources in correct order
          ---
          duration_ms: 0.3115
          type: 'test'
          ...
        # Subtest: should transform string values only once
        ok 4 - should transform string values only once
          ---
          duration_ms: 0.305416
          type: 'test'
          ...
        # Subtest: should enforce minimum constraints
        ok 5 - should enforce minimum constraints
          ---
          duration_ms: 0.172875
          type: 'test'
          ...
        # Subtest: should use an empty config when no config file is present
        ok 6 - should use an empty config when no config file is present
          ---
          duration_ms: 0.25425
          type: 'test'
          ...
        # Subtest: should handle generator-specific overrides
        ok 7 - should handle generator-specific overrides
          ---
          duration_ms: 0.340833
          type: 'test'
          ...
        1..7
    ok 4 - createRunConfiguration
      ---
      duration_ms: 3.230959
      type: 'suite'
      ...
    # Subtest: setConfig and getConfig
        # Subtest: should persist config across calls
        ok 1 - should persist config across calls
          ---
          duration_ms: 0.276209
          type: 'test'
          ...
        1..1
    ok 5 - setConfig and getConfig
      ---
      duration_ms: 0.339458
      type: 'suite'
      ...
    # Subtest: transformation optimization
        # Subtest: should transform changelog parsing only for strings
        ok 1 - should transform changelog parsing only for strings
          ---
          duration_ms: 0.406833
          type: 'test'
          ...
        # Subtest: should transform index parsing only for strings
        ok 2 - should transform index parsing only for strings
          ---
          duration_ms: 0.22975
          type: 'test'
          ...
        1..2
    ok 6 - transformation optimization
      ---
      duration_ms: 0.725042
      type: 'suite'
      ...
    1..6
ok 1 - config.mjs
  ---
  duration_ms: 10.071958
  type: 'suite'
  ...
1..1
# tests 17
# suites 7
# pass 17
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 418.699959
Redacted stderr
=== cmd 1: node --test --experimental-test-module-mocks src/utils/configuration/__tests__/index.test.mjs ===

- - - detach here - - -

External status

Mutable upstream observation; unattested and separate from the signed run record.

PR state
OPEN
Review signal
CHANGES REQUESTED
CI state
SUCCESS
Upstream updated at
2026-07-15T15:55:38Z
Observed at
2026-07-15T15:59:48.453Z
CHANGES REQUESTED

Linked maintainer review · open linked record

PR changed since this record. Recorded patch commit a0bdd2de6a81b60e2f8a607a40d39b4fa0b22751; current PR head observed at 2026-07-15T15:59:48.453Z: 662687f704f55e5e46b7479b24a031f156e236fb. The patch commit is declared source metadata, not an execution-bound identity; only the recorded patch bytes are bound to this receipt.

FOR MAINTAINERS

Request a private run

Maintain an open-source project? Send Northset a PR already in your queue. We run its repository-declared checks in an isolated container and return the run record privately. We do not modify the PR. Nothing is published without your approval. Free during the pilot.

The issue form is public. Do not include secrets or private repository details there; use email instead.

Already onboarded? Add northset-verify to a PR to request a run on that PR.

Claims boundary

This page reports scoped proof-of-pass receipt evidence. It does not prove code quality, security, full CI coverage, production readiness, or maintainer approval. An attestation confirms bundle provenance; it does not broaden the receipt's claim.

Read the full Claims Boundary policy.