Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

fix: add evidence format command and narrow ENOTDIR handling#228

Open
1919chichi wants to merge 15 commits into
rpamis:masterrpamis/comet:masterfrom
1919chichi:fix/native-evidence-format-and-fs-notdir1919chichi/comet:fix/native-evidence-format-and-fs-notdirCopy head branch name to clipboard
Open

fix: add evidence format command and narrow ENOTDIR handling#228
1919chichi wants to merge 15 commits into
rpamis:masterrpamis/comet:masterfrom
1919chichi:fix/native-evidence-format-and-fs-notdir1919chichi/comet:fix/native-evidence-format-and-fs-notdirCopy head branch name to clipboard

Conversation

@1919chichi

@1919chichi 1919chichi commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

✨ Summary

  • Adds comet native evidence format, which serializes acceptance evidence entries into the exact canonical Markdown block verification.md requires. The parser does a byte-for-byte comparison against a re-serialized canonical form (a deliberate defense against duplicate-key JSON smuggling — see native-acceptance.ts), so hand-typed JSON almost never matches regardless of formatting style. This command removes that friction entirely.
  • Narrows the ENOTDIR handling that was added for the plugin-marketplace crash fix (a stray file, e.g. .DS_Store, where a marketplace directory is expected) so it only applies to fileExists. removeFile/removeDir had picked up the same broadened check, which would have silently treated a removal hitting ENOTDIR as "already gone" instead of surfacing a real on-disk anomaly.
  • Syncs Chinese and English Native skill docs (SKILL.md, reference/artifacts.md, reference/commands.md) to point at the new command instead of describing hand-typed JSON.
  • Bumps to 0.4.0-beta.9 and updates CHANGELOG.md with both fixes under the same entry.

This PR bundles two fixes that would otherwise be separate contributions. Splitting them would require one to land first and claim beta.9 while the other rebases to beta.10, so they're kept together in one version bump per CONTRIBUTING.md's versioning rule.

Review follow-ups

  • Bounded evidence input: evidence format now reads --entries <path>/stdin through readBoundedEvidenceFile/readBoundedEvidenceStdin in native-cli.ts, capped at the existing MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES (1 MB) used elsewhere for Native evidence documents, instead of buffering an unbounded read before JSON.parse. comet-native-runtime.mjs is regenerated to match.
  • CHANGELOG order/classification: The [0.4.0-beta.9] entry is moved to the top of CHANGELOG.md (it had landed after beta.8/beta.7) and comet native evidence format is filed under a new ### Added section instead of ### Fixed, since it's a new command rather than a fix to previously broken behavior.
  • Not adopted: routing native-cli.ts's file/stdin reads through a platform/ adapter. domains/comet-native and domains/comet-classic already import fs directly in 30+ files (including stdin reads in classic-hook-guard.ts), and lint:architecture doesn't restrict this — platform/ in this repo owns platform-difference handling (e.g. the ENOTDIR fix above), not "no domain file may call fs". Happy to revisit if maintainers want that convention formalized.

🎯 Scope

  • CLI commands (init, status, doctor, update)
  • Core installer / platform detection
  • Comet skills (assets/skills/, assets/skills-zh/)
  • Comet shell scripts (assets/skills/comet/scripts/)
  • Tests / CI
  • Documentation / changelog
  • Other: comet native CLI (new evidence format subcommand)

🧪 Testing

  • pnpm build
  • pnpm lint
  • pnpm run lint:architecture
  • pnpm format:check
  • pnpm test
  • pnpm test -- test/domains/comet-classic/comet-scripts.test.ts — covered by the full pnpm test run above
  • Not run: —

Review follow-up commits (bounded evidence input, CHANGELOG reorder) were validated at the scope the changes touched, per this repo's risk-matched testing convention, rather than re-running the full suite:

  • pnpm build:native-runtime (regenerates comet-native-runtime.mjs) + npx vitest run test/repository/native-runtime-assets.test.ts
  • npx vitest run test/domains/comet-native/native-cli.test.ts and the full test/domains/comet-native directory — same 2 pre-existing TOCTOU failures as before, see Notes below
  • npx eslint / npx prettier --check on the changed files, node scripts/lint/architecture.mjs

✅ Checklist

  • PR title follows Conventional Commits, for example fix: handle project-scope init
  • User-facing behavior is documented in README.md, README-zh.md, or CONTRIBUTING.md — recorded in CHANGELOG.md per this repo's README-restraint convention
  • CHANGELOG.md is updated when behavior changes
  • Skill changes were made in Chinese first when applicable, then synced to English
  • New scripts are included in assets/manifest.json and relevant tests — no new scripts; assets/manifest.json's version was synced
  • Shell scripts remain portable across macOS, Linux, and Windows Git Bash — no shell scripts touched, only Node .mjs bundles
  • No unrelated generated files or local artifacts are included

👀 Notes for Reviewers

  • pnpm test has 2 pre-existing failures unrelated to this PR: native-check-receipt.test.ts ("detects a same-size replacement between lstat and open...") and native-snapshot.test.ts ("rejects a file identity swap..."). Both are timing-sensitive TOCTOU file-identity tests; I reproduced the identical failures on an unmodified origin/master checkout in a separate worktree, so they're not a regression from this branch. They still reproduce identically after the review follow-up commits.
  • The ENOTDIR scoping fix (platform/fs/file-system.ts) is verified with a regression test in test/platform/detect.test.ts that reproduces the original crash shape (a stray file where a plugin marketplace directory is expected), plus two new tests in test/platform/file-system.test.ts proving removeFile/removeDir now propagate ENOTDIR instead of masking it.
  • CodeRabbit also flagged missing try/finally env-var cleanup in the new test/platform/detect.test.ts case — noted, but left as-is for now since it follows the exact same pattern as the pre-existing neighboring test in that file; happy to fix both together in a follow-up if maintainers want it.

Summary by Sourcery

Introduce a native CLI subcommand for canonical acceptance evidence formatting and tighten filesystem error handling around plugin marketplace detection and removal helpers.

New Features:

  • Add comet native evidence format subcommand to serialize acceptance evidence entries into the canonical Markdown block required by verification docs.

Bug Fixes:

  • Stop comet init from crashing when the plugin marketplace cache contains stray non-directory files by treating ENOTDIR as a missing path only in existence checks.
  • Ensure removeFile and removeDir propagate ENOTDIR errors instead of masking them as already-removed paths.

Enhancements:

  • Update native skills documentation (English and Chinese) to describe the new evidence formatting workflow instead of hand-authored JSON.
  • Align bundled runtime code and helpers with the new evidence formatting and filesystem error classification behavior.

Build:

  • Bump package, lockfile, and assets manifest versions to 0.4.0-beta.9.

Documentation:

  • Document comet native evidence format usage in native skill references and SKILL guides in both English and Chinese.
  • Record the new CLI command and filesystem fixes in CHANGELOG for version 0.4.0-beta.9.

Tests:

  • Add native CLI tests covering evidence formatting behavior, stdin/file input validation, and canonical serialization enforcement.
  • Add platform detection and filesystem tests to cover ENOTDIR handling for plugin cache scanning and removal helpers.
  • Update CLI help tests to assert the new version and manifest metadata.

Summary by CodeRabbit

  • New Features
    • Added comet native evidence format to generate canonical verification.md acceptance-evidence blocks from JSON (stdin or --entries).
  • Bug Fixes
    • Fixed comet init crashing with ENOTDIR when stray files exist in the plugin cache path.
    • Improved missing-path handling (ENOENT/ENOTDIR) and updated removal behavior for ENOTDIR.
  • Documentation
    • Updated Native Verify/reference instructions to require pasting the generated canonical evidence block (no manual formatting).
  • Security
    • Hardened file reads with race-safe, identity-checked, bounded access and strict rejection of non-regular sources.
  • Tests
    • Expanded CLI and filesystem tests for evidence formatting, canonical serialization, and unsafe/race scenarios.

Adds `comet native evidence format`, which serializes acceptance
evidence entries into the exact canonical Markdown block
verification.md requires. The parser in native-acceptance.ts compares
the raw block byte-for-byte against a re-serialized canonical form (a
deliberate defense against duplicate-key JSON smuggling), so hand-typed
JSON almost never matches regardless of formatting style. This command
removes the need to hand-format the block at all.
Updates both Chinese and English Native SKILL.md and reference docs
(artifacts.md, commands.md) to direct agents to `comet native evidence
format` instead of hand-typing the acceptance evidence JSON block.
fileExists() treats ENOTDIR the same as ENOENT so a stray file where a
plugin marketplace directory is expected doesn't crash comet init.
removeFile()/removeDir() had picked up the same broadened check, which
would silently treat a removal hitting ENOTDIR as "already gone"
instead of surfacing a real on-disk anomaly. Scopes the ENOTDIR
handling back to fileExists only and adds regression coverage for both
the original crash and the removal behavior.
Syncs package.json, package-lock.json, assets/manifest.json, and the
version assertions in cli-help.test.ts. Adds the beta.9 changelog
entry covering the evidence-format command and the plugin marketplace
ENOTDIR crash fix.
@sourcery-ai

sourcery-ai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements a new comet native evidence format subcommand that produces the canonical acceptance evidence Markdown block from JSON entries, tightens ENOTDIR handling to only be treated as “missing” for existence checks (not removals), and updates docs/tests and versioning to reflect these behaviors.

Sequence diagram for the new comet native evidence format command

sequenceDiagram
  actor User
  participant NativeCLI as native_cli.dispatch
  participant FS as fs
  participant JSONParser as JSON
  participant Acceptance as serializeNativeVerificationMachineBlock
  participant Result as success

  User->>NativeCLI: dispatch(rawArgs, explicitProjectRoot)
  NativeCLI->>NativeCLI: requiredPositional(rawArgs, evidence subcommand)
  NativeCLI->>NativeCLI: takeOption(rawArgs, --entries)
  alt entriesPath provided
    NativeCLI->>FS: fs.readFile(path.resolve(entriesPath), utf8)
    FS-->>NativeCLI: raw
  else stdin JSON
    NativeCLI->>NativeCLI: process.stdin.isTTY check
    NativeCLI-->>User: NativeUsageError
    NativeCLI->>NativeCLI: readFileSync(0, utf8)
    NativeCLI-->>NativeCLI: raw
  end
  NativeCLI->>JSONParser: JSON.parse(raw)
  JSONParser-->>NativeCLI: entries
  NativeCLI->>NativeCLI: Array.isArray(entries)
  NativeCLI->>Acceptance: serializeNativeVerificationMachineBlock(entries)
  Acceptance-->>NativeCLI: block
  NativeCLI->>Result: success(evidence format, { block }, block + "\n")
  Result-->>User: CLI output with canonical Markdown block
Loading

File-Level Changes

Change Details Files
Add comet native evidence format CLI subcommand that converts acceptance evidence entries JSON into the exact canonical verification.md block.
  • Extend native CLI dispatch to handle evidence format [--entries <path>] with JSON input from stdin or a file.
  • Parse and validate acceptance evidence entries as a JSON array, surfacing usage and data errors with specific exit codes.
  • Use canonical serialization of validated entries and wrap them in acceptance evidence start/end markers, returning both JSON and text output.
  • Add native CLI tests covering successful canonical formatting, TTY-without-input usage errors, and malformed/non-array JSON rejection.
  • Wire the new command into generated runtime bundle and CLI help output so comet native --help advertises it.
domains/comet-native/native-cli.ts
assets/skills/comet-native/scripts/comet-native-runtime.mjs
test/domains/comet-native/native-cli.test.ts
test/app/cli-help.test.ts
Refine filesystem ENOTDIR handling so only read-only existence checks treat ENOTDIR as “missing”, while removal helpers now surface ENOTDIR as an error.
  • Change the helper used by file existence checks to consider both ENOENT and ENOTDIR as missing-path conditions.
  • Update removeFile and removeDir to treat only ENOENT as non-fatal and propagate ENOTDIR and other errors.
  • Add platform detection test to confirm plugin marketplace cache scanning skips stray non-directory entries instead of crashing.
  • Add filesystem tests to ensure removeFile and removeDir propagate ENOTDIR instead of masking it as "already gone".
  • Sync the bundled comet runtime filesystem helpers with the new missing-path semantics.
platform/fs/file-system.ts
test/platform/detect.test.ts
test/platform/file-system.test.ts
assets/skills/comet/scripts/comet-runtime.mjs
Update Native skill documentation to direct users to the new evidence formatting command and clarify canonical serialization behavior.
  • Revise artifact reference docs to explain using comet native evidence format to generate the acceptance evidence block and why hand-formatted JSON is rejected.
  • Add command reference entries describing the evidence format syntax, input sources, and output semantics in both English and Chinese.
  • Update SKILL guides to instruct using the new command when writing acceptance evidence, warning against hand-formatting the JSON block.
assets/skills/comet-native/reference/artifacts.md
assets/skills-zh/comet-native/reference/artifacts.md
assets/skills/comet-native/reference/commands.md
assets/skills-zh/comet-native/reference/commands.md
assets/skills/comet-native/SKILL.md
assets/skills-zh/comet-native/SKILL.md
Bump project version to 0.4.0-beta.9 and document the fixes in the changelog and assets manifest.
  • Increment package version from 0.4.0-beta.8 to 0.4.0-beta.9 in package metadata and asset manifest.
  • Adjust CLI help test expectations to assert the new version across package.json, package-lock.json, and assets manifest.
  • Add a new changelog entry for 0.4.0-beta.9 describing the native acceptance evidence formatting command and plugin marketplace ENOTDIR fix.
package.json
package-lock.json
assets/manifest.json
CHANGELOG.md
test/app/cli-help.test.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds comet native evidence format for canonical verification blocks, hardens file reads against races and symlinks, handles stray plugin-cache files without ENOTDIR crashes, adds regression coverage, and updates the project to 0.4.0-beta.9.

Changes

Native runtime and filesystem updates

Layer / File(s) Summary
Native evidence formatting command
domains/comet-native/native-cli.ts, assets/skills*/comet-native/..., assets/skills/comet-native/scripts/comet-native-runtime.mjs, test/domains/comet-native/native-cli.test.ts
Adds canonical evidence serialization from stdin or bounded files, validation, documentation, and CLI tests.
Shared race-safe filesystem reads
platform/fs/*, domains/comet-entry/current-selection.ts, domains/comet-native/native-lock.ts, assets/skills*/comet-native/scripts/*, assets/skills/comet/scripts/*
Adds regular-file, identity, real-path, bounded-read, symlink, FIFO, and replacement checks across selection, lock, protected-file, and evidence paths.
Filesystem edge-case handling
platform/fs/file-system.ts, test/platform/*, test/domains/comet-entry/*, test/domains/comet-native/*
Treats ENOTDIR as missing for existence checks while preserving removal errors, with filesystem and race-condition regression tests.
Beta.9 release metadata
package.json, assets/manifest.json, CHANGELOG.md, test/app/cli-help.test.ts
Updates release versions, changelog entries, and version assertions to 0.4.0-beta.9.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant NativeCLI
  participant EvidenceSerializer
  participant VerificationMd
  Operator->>NativeCLI: Provide entries via stdin or --entries
  NativeCLI->>EvidenceSerializer: Serialize entries into canonical marked block
  EvidenceSerializer-->>NativeCLI: Return formatted verification Markdown
  Operator->>VerificationMd: Paste generated acceptance evidence block
Loading

Possibly related PRs

  • rpamis/comet#115: Both changes cover plugin-cache scanning and hasPluginSuperpowers behavior.
  • rpamis/comet#195: Both changes concern the current-change selection file and its handling.
  • rpamis/comet#225: Both changes cover Native file identity comparison and validation paths.

Suggested reviewers: benym

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title names the two primary user-facing changes: the new evidence format command and the ENOTDIR handling refinement.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 49-55: Move the 0.4.0-beta.9 changelog entry above all older
release entries, add an “Added” section containing the comet native evidence
format change, and keep the plugin marketplace superpowers detection bullet
under a separate “Fixed” section.

In `@domains/comet-native/native-cli.ts`:
- Line 1: Replace direct filesystem and stdin imports in
domains/comet-native/native-cli.ts with the platform adapter, and update the
evidence-loading flow around lines 477-506 to read through its bounded Native
evidence-size API before JSON.parse. Keep native-cli focused on parsing and
serialization. Regenerate
assets/skills/comet-native/scripts/comet-native-runtime.mjs so the generated
asset reflects the source changes at lines 7371 and 28700-28730; do not
hand-edit the bundled output.

In `@test/platform/detect.test.ts`:
- Around line 492-515: Wrap the test body that modifies
process.env.CLAUDE_CONFIG_DIR and creates the plugin fixture in a try/finally
block. Move the existing restoration logic for origEnv into finally so
CLAUDE_CONFIG_DIR is restored or deleted even when setup or
hasPluginSuperpowers() fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 04c166c6-911c-47d3-b2ce-78387507cd1f

📥 Commits

Reviewing files that changed from the base of the PR and between 0b3c6ba and 6c879cd.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (17)
  • CHANGELOG.md
  • assets/manifest.json
  • assets/skills-zh/comet-native/SKILL.md
  • assets/skills-zh/comet-native/reference/artifacts.md
  • assets/skills-zh/comet-native/reference/commands.md
  • assets/skills/comet-native/SKILL.md
  • assets/skills/comet-native/reference/artifacts.md
  • assets/skills/comet-native/reference/commands.md
  • assets/skills/comet-native/scripts/comet-native-runtime.mjs
  • assets/skills/comet/scripts/comet-runtime.mjs
  • domains/comet-native/native-cli.ts
  • package.json
  • platform/fs/file-system.ts
  • test/app/cli-help.test.ts
  • test/domains/comet-native/native-cli.test.ts
  • test/platform/detect.test.ts
  • test/platform/file-system.test.ts

Comment thread CHANGELOG.md Outdated
Comment thread domains/comet-native/native-cli.ts Outdated
Comment on lines +492 to +515
const origEnv = process.env.CLAUDE_CONFIG_DIR;
const pluginDir = path.join(tmpDir, '.claude');
process.env.CLAUDE_CONFIG_DIR = pluginDir;

const cacheDir = path.join(pluginDir, 'plugins', 'cache');
await fs.mkdir(cacheDir, { recursive: true });
// A stray file (e.g. macOS .DS_Store) sitting where a marketplace
// directory is expected used to make fs.access throw ENOTDIR and crash
// comet init instead of being skipped like a missing marketplace.
await fs.writeFile(path.join(cacheDir, '.DS_Store'), '');

const skillsDir = path.join(cacheDir, 'test-marketplace', 'superpowers', '5.0.0', 'skills');
await fs.mkdir(skillsDir, { recursive: true });
await fs.mkdir(path.join(skillsDir, 'brainstorming'));
await fs.mkdir(path.join(skillsDir, 'using-superpowers'));

await expect(hasPluginSuperpowers()).resolves.toBe(true);

if (origEnv !== undefined) {
process.env.CLAUDE_CONFIG_DIR = origEnv;
} else {
delete process.env.CLAUDE_CONFIG_DIR;
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Restore CLAUDE_CONFIG_DIR in a finally block.

If directory setup or the assertion fails, the cleanup at Lines 510-515 is skipped and the process-wide environment variable leaks into subsequent tests. Wrap the test body in try/finally.

Proposed fix
       const origEnv = process.env.CLAUDE_CONFIG_DIR;
       const pluginDir = path.join(tmpDir, '.claude');
       process.env.CLAUDE_CONFIG_DIR = pluginDir;

-      const cacheDir = path.join(pluginDir, 'plugins', 'cache');
-      await fs.mkdir(cacheDir, { recursive: true });
-      await fs.writeFile(path.join(cacheDir, '.DS_Store'), '');
-
-      const skillsDir = path.join(cacheDir, 'test-marketplace', 'superpowers', '5.0.0', 'skills');
-      await fs.mkdir(skillsDir, { recursive: true });
-      await fs.mkdir(path.join(skillsDir, 'brainstorming'));
-      await fs.mkdir(path.join(skillsDir, 'using-superpowers'));
-
-      await expect(hasPluginSuperpowers()).resolves.toBe(true);
-
-      if (origEnv !== undefined) {
-        process.env.CLAUDE_CONFIG_DIR = origEnv;
-      } else {
-        delete process.env.CLAUDE_CONFIG_DIR;
+      try {
+        const cacheDir = path.join(pluginDir, 'plugins', 'cache');
+        await fs.mkdir(cacheDir, { recursive: true });
+        await fs.writeFile(path.join(cacheDir, '.DS_Store'), '');
+        const skillsDir = path.join(
+          cacheDir,
+          'test-marketplace',
+          'superpowers',
+          '5.0.0',
+          'skills',
+        );
+        await fs.mkdir(skillsDir, { recursive: true });
+        await fs.mkdir(path.join(skillsDir, 'brainstorming'));
+        await fs.mkdir(path.join(skillsDir, 'using-superpowers'));
+        await expect(hasPluginSuperpowers()).resolves.toBe(true);
+      } finally {
+        if (origEnv !== undefined) {
+          process.env.CLAUDE_CONFIG_DIR = origEnv;
+        } else {
+          delete process.env.CLAUDE_CONFIG_DIR;
+        }
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const origEnv = process.env.CLAUDE_CONFIG_DIR;
const pluginDir = path.join(tmpDir, '.claude');
process.env.CLAUDE_CONFIG_DIR = pluginDir;
const cacheDir = path.join(pluginDir, 'plugins', 'cache');
await fs.mkdir(cacheDir, { recursive: true });
// A stray file (e.g. macOS .DS_Store) sitting where a marketplace
// directory is expected used to make fs.access throw ENOTDIR and crash
// comet init instead of being skipped like a missing marketplace.
await fs.writeFile(path.join(cacheDir, '.DS_Store'), '');
const skillsDir = path.join(cacheDir, 'test-marketplace', 'superpowers', '5.0.0', 'skills');
await fs.mkdir(skillsDir, { recursive: true });
await fs.mkdir(path.join(skillsDir, 'brainstorming'));
await fs.mkdir(path.join(skillsDir, 'using-superpowers'));
await expect(hasPluginSuperpowers()).resolves.toBe(true);
if (origEnv !== undefined) {
process.env.CLAUDE_CONFIG_DIR = origEnv;
} else {
delete process.env.CLAUDE_CONFIG_DIR;
}
});
const origEnv = process.env.CLAUDE_CONFIG_DIR;
const pluginDir = path.join(tmpDir, '.claude');
process.env.CLAUDE_CONFIG_DIR = pluginDir;
try {
const cacheDir = path.join(pluginDir, 'plugins', 'cache');
await fs.mkdir(cacheDir, { recursive: true });
await fs.writeFile(path.join(cacheDir, '.DS_Store'), '');
const skillsDir = path.join(cacheDir, 'test-marketplace', 'superpowers', '5.0.0', 'skills');
await fs.mkdir(skillsDir, { recursive: true });
await fs.mkdir(path.join(skillsDir, 'brainstorming'));
await fs.mkdir(path.join(skillsDir, 'using-superpowers'));
await expect(hasPluginSuperpowers()).resolves.toBe(true);
} finally {
if (origEnv !== undefined) {
process.env.CLAUDE_CONFIG_DIR = origEnv;
} else {
delete process.env.CLAUDE_CONFIG_DIR;
}
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 500-500: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.writeFile(path.join(cacheDir, '.DS_Store'), '')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/platform/detect.test.ts` around lines 492 - 515, Wrap the test body that
modifies process.env.CLAUDE_CONFIG_DIR and creates the plugin fixture in a
try/finally block. Move the existing restoration logic for origEnv into finally
so CLAUDE_CONFIG_DIR is restored or deleted even when setup or
hasPluginSuperpowers() fails.

1919chichi and others added 2 commits July 23, 2026 21:10
`comet native evidence format` read the whole --entries file or stdin
into memory via fs.readFile/readFileSync before JSON.parse, with no
size limit — unlike the rest of the Native evidence subsystem, which
enforces explicit byte caps on evidence-shaped documents
(native-verification-scope.ts, native-change.ts, native-run-store.ts).

Adds readBoundedEvidenceFile (stat-then-read) and
readBoundedEvidenceStdin (aborts mid-stream once the cap is exceeded),
both bounded by the existing MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES
constant, and regenerates comet-native-runtime.mjs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NuxYieafr2jRwiL6KzPs43
The [0.4.0-beta.9] entry was inserted after beta.8/beta.7 instead of
at the top, and comet native evidence format — a new command — was
filed entirely under Fixed. Moves the entry to the top of the file
and gives it an Added section for the new command, keeping the
ENOTDIR and greeting-workflow fixes under Fixed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NuxYieafr2jRwiL6KzPs43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/domains/comet-native/native-cli.test.ts (1)

759-780: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add an oversized-stdin regression test.

This test covers only --entries files. Add a piped stdin payload larger than MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES and assert the same rejection, since stdin has separate mid-stream limit logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/domains/comet-native/native-cli.test.ts` around lines 759 - 780, Add a
regression test alongside the existing oversized entries-file test that pipes a
payload larger than MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES into the
native CLI evidence formatting command via stdin. Invoke runNativeCli with the
stdin input and assert exit code 65 plus the same invalid-data error containing
“exceeds”, covering the separate stdin size-limit path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@domains/comet-native/native-cli.ts`:
- Around line 190-195: Update readBoundedEvidenceFile to enforce the limit
during reading rather than relying on the initial fs.stat check: open the file
and read through the descriptor or stream with a hard maxBytes + 1 bound, then
reject when more than maxBytes is obtained. Preserve the existing success result
for files within the limit and the descriptive size-limit error for oversized
input.

---

Nitpick comments:
In `@test/domains/comet-native/native-cli.test.ts`:
- Around line 759-780: Add a regression test alongside the existing oversized
entries-file test that pipes a payload larger than
MAX_NATIVE_IMPLEMENTATION_EVIDENCE_DOCUMENT_BYTES into the native CLI evidence
formatting command via stdin. Invoke runNativeCli with the stdin input and
assert exit code 65 plus the same invalid-data error containing “exceeds”,
covering the separate stdin size-limit path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1879cdcc-1793-4fc7-aaa8-fa948e7f514e

📥 Commits

Reviewing files that changed from the base of the PR and between 6c879cd and c78ddea.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • assets/skills/comet-native/scripts/comet-native-runtime.mjs
  • domains/comet-native/native-cli.ts
  • test/domains/comet-native/native-cli.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • assets/skills/comet-native/scripts/comet-native-runtime.mjs

Comment thread domains/comet-native/native-cli.ts Outdated
@benym

benym commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

还有2项需要处理

  1. domains/comet-native/native-cli.ts 中 readBoundedEvidenceFile 先执行 stat,再通过 readFile 完整读取文件。文件可能在两次调用之间被替换或增大,FIFO 等特殊文件也可能绕过 stat.size 检查,因此目前不能真正保证 1 MiB 上限。建议打开同一个文件句柄并进行有界分块读取,最多读取 maxBytes + 1,同时补充文件增长或替换场景的回归测试。

  2. CHANGELOG.md 中的 “First-contributor greeting workflow” 条目不属于本 PR,也不是从 0.4.0-beta.8 到当前 head 的实际变化;相关 workflow 没有改动。这条发布说明需要删除。

1919chichi and others added 2 commits July 24, 2026 11:18
- Replace fs.stat + fs.readFile with fs.open + bounded chunked reads
  using non-blocking flags so special files (e.g. FIFOs) cannot hang
  the evidence format command
- Reject non-regular-file paths for --entries with a clear error
- Sync generated comet-native-runtime.mjs and add FIFO regression test
- Remove unrelated changelog entry not part of this branch
readCometCurrentSelection checked .comet/current-change.json with a
path-based lstat/size check and then reopened the same path with
readFile. Between the two calls the file could be grown past the
16 KiB limit or swapped for a symlink, bypassing both the byte bound
and the regular-file check. This path is reachable from the Hook
Router (every hooked tool call), comet doctor, comet resume-probe,
and both Native and Classic change selection.

Mirror the fd-based bounded read already used by
readBoundedEvidenceFile in native-cli.ts: open once with
O_NOFOLLOW | O_NONBLOCK, fstat the handle, and read through the same
descriptor with a hard maxBytes + 1 bound, so the check and the read
observe the same file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NuxYieafr2jRwiL6KzPs43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
assets/skills/comet-native/scripts/comet-native-runtime.mjs (1)

28441-28479: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

readBoundedEvidenceFile opens without O_NOFOLLOW, unlike every sibling reader touched in this diff.

readCometCurrentSelection (line ~21477), native-evidence-retention.ts's readCanonicalDocument (line ~24223), and native-check-receipt.ts's readScopedFile (line ~26548) all pair O_NONBLOCK with O_NOFOLLOW on non-win32. This new helper for comet native evidence format --entries <path> only sets O_NONBLOCK, so a symlink at the given path is silently followed. Given the CLI accepts an arbitrary path argument, this reintroduces the symlink-following class of issue the rest of the PR is fixing (e.g. an attacker-writable shared/tmp directory could substitute a symlink before the command runs).

This is a generated asset; the fix belongs in domains/comet-native/native-cli.ts and must be resynced via the build.

🔒 Proposed fix (apply in domains/comet-native/native-cli.ts, then rebuild)
-  const flags = process.platform === "win32" ? "r" : fsConstants7.O_RDONLY | fsConstants7.O_NONBLOCK;
-  const handle = await fs33.open(filePath, flags);
+  const flags = process.platform === "win32" ? "r" : fsConstants7.O_RDONLY | fsConstants7.O_NOFOLLOW | fsConstants7.O_NONBLOCK;
+  const handle = await fs33.open(filePath, flags).catch((error) => {
+    if (error.code === "ELOOP") {
+      throw new Error(`Acceptance evidence entries path must be a regular file: ${filePath}`, { cause: error });
+    }
+    throw error;
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@assets/skills/comet-native/scripts/comet-native-runtime.mjs` around lines
28441 - 28479, Update readBoundedEvidenceFile to include O_NOFOLLOW alongside
O_NONBLOCK in the non-Windows open flags, matching the sibling readers’ symlink
protection. Apply the source change in native-cli.ts, then rebuild to
resynchronize the generated comet-native-runtime.mjs asset.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@assets/skills/comet-native/scripts/comet-native-runtime.mjs`:
- Around line 28441-28479: Update readBoundedEvidenceFile to include O_NOFOLLOW
alongside O_NONBLOCK in the non-Windows open flags, matching the sibling
readers’ symlink protection. Apply the source change in native-cli.ts, then
rebuild to resynchronize the generated comet-native-runtime.mjs asset.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e09fae8a-61ce-4b37-9bdd-7445ee9c7359

📥 Commits

Reviewing files that changed from the base of the PR and between c3578e9 and 7091389.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • assets/skills/comet-native/scripts/comet-native-runtime.mjs
  • assets/skills/comet/scripts/comet-hook-router.mjs
  • assets/skills/comet/scripts/comet-runtime.mjs
  • domains/comet-entry/current-selection.ts
  • test/domains/comet-entry/current-selection.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md

@1919chichi

1919chichi commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@benym 感谢 review,这两个问题已经在当前分支修复(c3578e9f)。

顺便复查同类问题时,发现 domains/comet-entry/current-selection.ts 里的 readCometCurrentSelection 也是同样的先 lstat 校验、再单独 readFile 的写法,而且这是 Hook Router 每次工具调用都会走的高频路径,所以也一并按同样思路(单次打开、同一描述符读取)在 7091389 中修复,并补充了能稳定复现竞态窗口的回归测试。

@benym

benym commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

还有一些问题需要处理
current-selection.ts:119 在 Windows 仅使用 O_RDONLY。这会跟随 .comet/current-change.json 的符号链接,回退了 master 原先的拒绝行为,也与 PR 声称的 NOFOLLOW 防护不一致。该读取由 Hook Router、doctor、resume-probe 等真实路径调用。Node 22 官方文档也明确说明 Windows 不提供 O_NOFOLLOW,因此需要额外的 Windows 文件身份/链接检查,不能只靠当前分支判断。对应Node.js 文档

native-cli.ts:190 的 readBoundedEvidenceFile 在非 Windows 也漏了 O_NOFOLLOW,因此 --entries 会跟随链接并把目标当作普通文件。这正是最新 CodeRabbit 尚未处理的评论。建议同时补 symlink 回归测试,并重新生成 Native runtime。

1919chichi and others added 7 commits July 24, 2026 15:52
… relying on O_NOFOLLOW

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NuxYieafr2jRwiL6KzPs43
…id-read swaps

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NuxYieafr2jRwiL6KzPs43
@1919chichi

1919chichi commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@benym 两个问题都已在最新推送中修复(01a459c018faa067)。

你指出的两处读取都可能在检查和读取之间被调包——.comet/current-change.json(Hook Router 每次工具调用、comet doctorcomet resume-probe 都会读)在 Windows 上被换成 symlink 后会把链接目标的内容当合法数据读出;evidence format --entries <symlink> 在所有平台都会跟随链接。复查同形状代码时还发现第三处:Native 锁文件读取没有打开前检查,遇到 FIFO 会永久阻塞,symlink 要读完内容才拒绝。

这三处读取现在统一走新增的共享读取函数 platform/fs/race-safe-read.ts(readFileRaceSafe)——打开前 lstat 拒绝 symlink/FIFO/非普通文件,打开后与读完后各核对一次文件身份(dev/ino + realpath),中途被调包就报错中止,不会再把调包后的内容当成功返回。对应提交:865222d0(selection)、9c56097b(evidence entries,含你建议的 symlink 回归测试和 Native runtime 重建)、9aca8da0(lock,含 symlink/FIFO 回归测试);该函数本身有 13 个单元测试覆盖三个检查点、竞态调包、超限和 bigint 精度。

防护不再依赖 O_NOFOLLOW,Windows(Node 不提供该 flag)与 POSIX 走同一份身份复核代码,防护等级一致。由于本地与 CI 均无 Windows 环境,这一结论是基于两平台共享同一代码路径的推理覆盖,已在测试文件中显式注明,而非实际 Windows 执行覆盖。

仓库里还有 6 处文件(native-bounded-file.tsnative-run-store.tsnative-protected-file.tsnative-check-receipt.tsnative-check-receipt-storage.tsnative-evidence-retention.ts)各自独立维护同款"lstat 前置 + 身份复核"验证逻辑,细节互有漂移——这正是本次漏洞的根因:新代码只能就近抄某一份,抄漏无人发现。计划在后续 PR 中把这些散落的验证逻辑逐个收敛到这个共享实现,以各文件现有竞态测试零改动通过作为行为不变的证明,收敛完成后再评估是否加架构 lint 强制约束。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
domains/comet-native/native-lock.ts (1)

161-165: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Wrap JSON.parse so a malformed lock file yields a descriptive error.

JSON.parse(bytes.toString('utf8')) sits outside the try/catch, so a corrupt-but-regular lock file throws a raw SyntaxError instead of the Invalid Native lock metadata: <file> message that parseNativeLockOwner gives for structural problems. Consider parsing inside a guarded block for consistent diagnostics.

♻️ Suggested guard
   return {
     file,
-    owner: parseNativeLockOwner(JSON.parse(bytes.toString('utf8')) as unknown, file),
+    owner: parseNativeLockOwner(parseLockJson(bytes.toString('utf8'), file), file),
     identity: nativeLockFileIdentity(stat),
   };

where parseLockJson catches SyntaxError and rethrows Invalid Native lock metadata: ${file}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@domains/comet-native/native-lock.ts` around lines 161 - 165, Move the
JSON.parse operation in the native lock parsing flow into the existing guarded
try/catch, such as through the parseLockJson helper, so malformed lock contents
are caught and rethrown with the same “Invalid Native lock metadata: <file>”
diagnostic used by parseNativeLockOwner. Preserve the existing owner and
identity construction for valid lock files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@domains/comet-native/native-lock.ts`:
- Around line 161-165: Move the JSON.parse operation in the native lock parsing
flow into the existing guarded try/catch, such as through the parseLockJson
helper, so malformed lock contents are caught and rethrown with the same
“Invalid Native lock metadata: <file>” diagnostic used by parseNativeLockOwner.
Preserve the existing owner and identity construction for valid lock files.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: db5bedc4-5268-487d-8f46-8453f2e49e98

📥 Commits

Reviewing files that changed from the base of the PR and between 7091389 and 18faa06.

📒 Files selected for processing (15)
  • CHANGELOG.md
  • assets/skills/comet-native/scripts/comet-native-runtime.mjs
  • assets/skills/comet/scripts/comet-entry-runtime.mjs
  • assets/skills/comet/scripts/comet-hook-router.mjs
  • assets/skills/comet/scripts/comet-runtime.mjs
  • domains/comet-entry/current-selection.ts
  • domains/comet-native/native-cli.ts
  • domains/comet-native/native-file-identity.ts
  • domains/comet-native/native-lock.ts
  • platform/fs/file-identity.ts
  • platform/fs/race-safe-read.ts
  • test/domains/comet-entry/current-selection.test.ts
  • test/domains/comet-native/native-cli.test.ts
  • test/domains/comet-native/native-lock.test.ts
  • test/platform/race-safe-read.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • CHANGELOG.md
  • test/domains/comet-entry/current-selection.test.ts
  • assets/skills/comet/scripts/comet-hook-router.mjs
  • assets/skills/comet/scripts/comet-runtime.mjs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.