Skip to content

Conversation

@flyingrobots
Copy link
Member

@flyingrobots flyingrobots commented Oct 8, 2025

Summary

  • add shared helper to create temp repos via filesystem port
  • update libgit2 unit tests to avoid writing .gm_* artifacts to the workspace
  • change make ci-local to stage a copy before running the Docker build and tests

Testing

  • make ci-local

Summary by CodeRabbit

  • New Features

    • Expanded CLI Quickstart and new CLI commands (list, cache-rebuild); added debug-container helper and a formatter-based logging/telemetry seam with JSON/text renderer.
  • Bug Fixes

    • Improved diagnostics lifecycle and disposal, safer canonicalization to prevent double-free, more robust ref/path handling, and stabilized timing/telemetry metrics.
  • Documentation

    • Expanded docs for CLI, telemetry/formatter, diagnostics, debug container, and operational guides.
  • Chores

    • CI: local staging/artifact sync; removed obsolete pre-push worksheet gate and feedback workflow.
  • Tests

    • New test support library, added integration/regression test, and extended unit coverage.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

Walkthrough

Adds a public telemetry log-format API and formatter seam, disposables for diagnostics and crypto contexts, capacity-aware journal codec/decoder and monotonic timing, fs-temp canonicalization/view safety, shared test temp-repo helpers and test support lib, CI/debug staging tooling, numerous adapters/header/test updates, and related docs.

Changes

Cohort / File(s) Summary
Telemetry & Log Formatter
core/include/gitmind/telemetry/log_format.h, core/include/gitmind/telemetry/internal/log_format.h, core/src/telemetry/log_format.c, core/src/telemetry/config.c, core/include/gitmind/context.h, docs/operations/Telemetry_Config.md
Introduce public gm_log_kv_t/gm_log_formatter_fn/gm_log_format_render_default; move/internal header to forward to public API; deterministic JSON ordering, robust escaping, formatter seam, repo-hash helpers, gm_telemetry_tag_context_t and context-based gm_telemetry_build_tags.
Diagnostics port & adapters
core/include/gitmind/ports/diagnostic_port.h, core/include/gitmind/adapters/diagnostics/stderr_diagnostics_adapter.h, core/src/adapters/diagnostics/stderr_diagnostics_adapter.c, core/tests/fakes/diagnostics/*, docs/operations/Diagnostics_Events.md
Add vtable dispose hook and gm_diag_reset helper; stderr adapter gains emit_enabled, escaped output and explicit dispose path; fake diagnostics port exposes/init+dispose wiring; docs show teardown usage.
CLI wiring & docs
apps/cli/main.c, apps/cli/main.md, apps/cli/README.md, README.md
Switch to public adapter headers; guarded init storing dispose callbacks for diagnostics/logger; robust disposal fallback; expand CLI quickstart, porcelain/JSON examples and diagnostics notes.
Journal codec / decoder / timing
core/include/gitmind/journal/internal/codec.h, core/include/gitmind/journal/internal/read_decoder.h, core/include/gitmind/journal/internal/append_plan.h, core/src/domain/journal/codec.c, core/src/domain/journal/read_decoder.c, core/src/domain/journal/append_planner.c, core/src/journal/reader.c, core/src/journal/writer.c
Switch to URLSAFE_NO_PADDING; encode now reports payload_len excluding NUL; decode API adds decoded_capacity and capacity checks; stricter capacity-aware decoding/conversion; rename empty_tree_oid→tree_oid; reader/writer use monotonic_ms_now and integrate edge-count and canonicalization view handling.
Cache & OID prefix
core/include/gitmind/cache/internal/oid_prefix.h, core/src/domain/cache/oid_prefix.c, core/src/app/cache/cache_rebuild_service.c, core/tests/unit/test_cache_oid_prefix.c
Document rationale and limits; migrate to size_t arithmetic, conservative clamping and early returns; propagate actual error codes; expand unit tests for rounding, small buffers, and boundaries.
Git / libgit2 adapter & refs
core/src/adapters/git/libgit2_repository_port.c
Commit signature fallback only on ENOTFOUND with clearer errors; reference update normalizes names, rejects traversal, validates lengths, creates parent dirs with 0775, returns precise errors.
Logger adapter & includes
core/src/adapters/logging/stdio_logger_adapter.c, core/include/gitmind/adapters/logging/stdio_logger_adapter.h
Update include paths; add <stdio.h>; cross-platform gmtime_s/gmtime_r usage; header adds gitmind/result.h.
Ports & fs_temp semantics
core/src/ports/cache/cache_query_port.c, core/include/gitmind/ports/fs_temp_port.h, docs/architecture/fs_temp_service_plan.md
Zero-initialize cache query outputs; document that canonicalize_ex returns a _view into adapter-owned scratch storage which callers must copy/promote before reuse.
Test support & helpers
core/tests/support/temp_repo_helpers.c, core/tests/support/temp_repo_helpers.h, core/tests/unit/support/temp_repo_helpers.h, meson.build
Add test temp-repo helpers, provider model, explicit make/cleanup APIs; introduce gitmind_test_support static lib and link tests; add new integration test target.
Tests: updates & new
core/tests/integration/*, core/tests/unit/*
New integration regression for cache canonicalize; unit tests updated for log_format JSON escaping/order, telemetry context usage, diagnostics disposal, env handling, and cache oid_prefix coverage.
CI & debug tooling
tools/ci/ci_local.sh, tools/debug/debug-container.sh, docs/operations/Debug_Container.md, AGENTS.md
ci_local: staging workspace snapshot, rsync/tar fallback, artifact collection, clang-tidy diffing and propagate container exit; add debug-container script and docs for staged debugging workflow; AGENTS.md updated with CI/telemetry notes.
Hooks & workflows
.githooks/pre-push, .github/workflows/apply-feedback.yml
Removed worksheet enforcement from pre-push; deleted the Apply PR Feedback workflow.
Crypto context dispose
core/include/gitmind/crypto/backend.h, core/src/crypto/backend.c
Add gm_crypto_context_dispose public API to clear crypto backend references.
POSIX fs_temp & large refactor
core/src/adapters/fs/posix_temp_adapter.c
Major canonicalization and path helpers refactor, safer directory creation/removal, helper utilities, rename vtbl to GmPosixFsTempVtbl and related internal API cleanup.
Misc includes, lint & config
core/src/adapters/metrics/null_metrics_adapter.h, .markdownlint-cli2.jsonc, .markdownlintignore, docs/activity/*
Add missing result.h includes where needed; add markdownlint config/ignore; expand activity docs summarizing CI/tooling/formatter changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI as apps/cli/main
  participant Diag as gm_diagnostics_port
  participant Stderr as stderr_adapter
  participant Logger as logger_port

  User->>CLI: run git-mind command
  CLI->>Diag: init stderr adapter
  Diag->>Stderr: allocate state, emit_enabled = true
  alt init ok
    Stderr-->>CLI: provide vtbl + dispose callback
  else init failed
    Stderr-->>CLI: free error
  end
  CLI->>Logger: init stdio logger (store dispose)
  CLI->>User: run subcommand
  CLI->>Diag: gm_diag_reset(&diag_port) on shutdown
  note right of Diag: gm_diag_reset calls vtbl.dispose if present\nand clears port pointers
  CLI->>Logger: call logger_port_dispose() or fallback stdio dispose
  CLI-->>User: exit
Loading
sequenceDiagram
  autonumber
  participant Writer as journal/writer
  participant Timer as monotonic_ms_now
  participant Codec as journal/codec
  participant Reader as journal/reader

  Writer->>Timer: monotonic_ms_now()
  Timer-->>Writer: start_ms
  Writer->>Codec: gm_journal_encode_message(message) [URLSAFE_NO_PADDING]
  Codec-->>Writer: encoded payload (payload_len excludes NUL)
  Writer->>Writer: write entry
  Reader->>Timer: monotonic_ms_now()
  Reader->>Codec: gm_journal_decode_message(raw, buf, decoded_capacity, &decoded_len)
  alt decode ok
    Codec-->>Reader: decoded_len
    Reader->>Reader: process edges, increment edge_count (if pointer provided)
  else error
    Codec-->>Reader: error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

I nibble logs in tidy rows and sort each kv pair,
I hush the stderr chatter, then tidy up with care.
I count monotonic heartbeats, copy views before I roam,
I seed a temp repo, run twice, and bring each test back home.
A rabbit’s patch: small hops, safe paths, and one tidy comb. 🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description only provides a high-level summary and testing steps but omits several required sections from the repository’s template such as Change Areas, Risk assessment, and Code Review Guidance. Please update the description to include the template’s Change Areas checklist, specify the Risk categories that apply, and add the Code Review Guidance section to fully conform to the required format.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.03% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the two primary modifications described by the author—refactoring temporary repository support and isolating the CI workspace—using clear and specific language without extraneous detail.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/temp-port-ci-copy

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 and usage tips.

@flyingrobots flyingrobots changed the base branch from feat/hex-ports-ci-green to main October 8, 2025 17:31
@flyingrobots
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Caution

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

⚠️ Outside diff range comments (2)
core/src/journal/writer.c (2)

290-296: Propagate real error codes from flush_journal_batch

Current code returns GM_ERR_UNKNOWN, masking the underlying cause. Return the actual status.

-        if (should_flush_buffer(offset)) {
-            if (flush_journal_batch(jctx, buffer, offset) != GM_OK) {
-                return GM_ERR_UNKNOWN;
-            }
-            offset = 0U;
-        }
+        if (should_flush_buffer(offset)) {
+            int rc = flush_journal_batch(jctx, buffer, offset);
+            if (rc != GM_OK) {
+                return rc;
+            }
+            offset = 0U;
+        }
...
-    if (offset > 0U) {
-        if (flush_journal_batch(jctx, buffer, offset) != GM_OK) {
-            return GM_ERR_UNKNOWN;
-        }
-    }
+    if (offset > 0U) {
+        int rc = flush_journal_batch(jctx, buffer, offset);
+        if (rc != GM_OK) {
+            return rc;
+        }
+    }

Also applies to: 298-302


452-472: Validate inputs in gm_journal_create_commit

Missing null checks can dereference ctx/ref or data. Add fast-fail validation.

 int gm_journal_create_commit(gm_context_t *ctx, const char *ref,
                           const void *data, size_t len) {
     journal_ctx_t jctx;
     gm_oid_t commit_oid;
 
+    /* Argument validation */
+    if (ctx == NULL || ref == NULL || data == NULL) {
+        return GM_ERR_INVALID_ARGUMENT;
+    }
+
     /* Initialize context */
     jctx.repo_port = &ctx->git_repo_port;
🧹 Nitpick comments (53)
docs/code-reviews/PR178/a39bfee82cb78194ff33fc588d43321c98d9cc8c.md (4)

2-2: Use a human-readable title for discoverability.

Prefer a descriptive title including PR number and topic.

-title: a39bfee82cb78194ff33fc588d43321c98d9cc8c.md
+title: PR178: Refactor temp repos and isolate CI workspace (a39bfee)

14-14: Linkify the commit SHA in the review log.

Improves navigation from the archive.

-| 2025-10-08 | CodeRabbit (and reviewers) | `a39bfee82cb78194ff33fc588d43321c98d9cc8c` | [chore/temp-port-ci-copy](https://github.com/neuroglyph/git-mind/tree/chore/temp-port-ci-copy "neuroglyph/git-mind:chore/temp-port-ci-copy") | [PR#178](https://github.com/neuroglyph/git-mind/pull/178) |
+| 2025-10-08 | CodeRabbit (and reviewers) | [`a39bfee`](https://github.com/neuroglyph/git-mind/commit/a39bfee82cb78194ff33fc588d43321c98d9cc8c) | [chore/temp-port-ci-copy](https://github.com/neuroglyph/git-mind/tree/chore/temp-port-ci-copy "neuroglyph/git-mind:chore/temp-port-ci-copy") | [PR#178](https://github.com/neuroglyph/git-mind/pull/178) |

44-50: Strip transient checkbox metadata; keep archive clean.

The JSON attributes and branch-specific checkbox are ephemeral. Convert to plain TODOs.

-<summary>🧪 Generate unit tests (beta)</summary>
+<summary>🧪 Unit tests TODOs</summary>
@@
-- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with unit tests
-- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post copyable unit tests in a comment
-- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit unit tests in branch `chore/temp-port-ci-copy`
+- Create PR with unit tests
+- Post copyable unit tests in a comment
+- Commit unit tests in branch `chore/temp-port-ci-copy`

77-77: Optional: add commit context to Meta.

Include the commit link next to the comment for quick cross-reference.

-_Meta_: https://github.com/neuroglyph/git-mind/pull/178#issuecomment-3382223172
+_Meta_: https://github.com/neuroglyph/git-mind/pull/178#issuecomment-3382223172 · Commit: https://github.com/neuroglyph/git-mind/commit/a39bfee82cb78194ff33fc588d43321c98d9cc8c
docs/code-reviews/PR178/1a49c6f455c33f1dcf87905f804020b6654be938.md (4)

2-2: Use a human-readable title (avoid file-name/extension).

Consider a descriptive title like “PR 178 — CI workspace isolation and temp repo helpers” and drop the “.md”.


14-14: Verify row metadata (SHA/branch/PR) corresponds to the archived commit.

Please confirm the SHA matches the content referenced by this doc and, if useful, include the commit subject for context.


22-35: Make the config instruction explicit with a YAML snippet.

Add a concrete example so readers can copy/paste:

# .coderabbit.yaml
reviews:
  review_status: false

Also note explicitly which branch is the default (main) to reduce ambiguity.


44-49: Checkbox attributes and hardcoded branch may age poorly.

  • The embedded JSON attributes in the checkboxes may leak into the rendered page. If the renderer doesn’t hide them, switch to plain GitHub-style checkboxes.
  • “Commit unit tests in branch chore/temp-port-ci-copy” will become stale post-merge. Prefer a generic instruction or placeholder.
tools/ci/ci_local.sh (4)

12-17: Optional: Allow keeping stage dir for debugging and broaden signal handling

Add a guard env var to keep the staged workspace on failure; optionally trap INT/TERM too. Keeps artifacts for inspection.

-cleanup() {
-  if [ -n "${CI_LOCAL_STAGE_DIR:-}" ] && [ -d "$CI_LOCAL_STAGE_DIR" ]; then
-    rm -rf "$CI_LOCAL_STAGE_DIR"
-  fi
-}
-trap cleanup EXIT
+cleanup() {
+  if [ -n "${CI_LOCAL_STAGE_DIR:-}" ] && [ -d "$CI_LOCAL_STAGE_DIR" ]; then
+    if [ -n "${GITMIND_KEEP_STAGE:-}" ]; then
+      echo "Keeping staging dir: $CI_LOCAL_STAGE_DIR"
+    else
+      rm -rf "$CI_LOCAL_STAGE_DIR"
+    fi
+  fi
+}
+trap cleanup EXIT INT TERM

62-75: Respect .gitignore during rsync copy

Reduces noise and size of the staged workspace. Tar fallback can stay as-is.

-if command -v rsync >/dev/null 2>&1; then
-  rsync -a --delete \
+if command -v rsync >/dev/null 2>&1; then
+  rsync -a --delete --filter=':- .gitignore' \
     --exclude '/build-local/' \
     --exclude '/ci_logs.zip' \
     --exclude '/clang-tidy-report.txt' \
     --exclude '/clang-tidy-report-full.txt' \
     "$ROOT_DIR"/ "$WORKSPACE_COPY"/

78-78: Status propagation: quote variables

Defensive quoting avoids edge cases even with set -u/-e.

-set +e
+set +e
@@
-status=$?
+status=$?
 set -e
@@
-[ $status -eq 0 ] || exit $status
+[ "$status" -eq 0 ] || exit "$status"

Also applies to: 105-107, 127-127


108-117: Avoid duplicate compile_commands.json copy

You already copy compile_commands.json in-container to workspace root; then you copy it again from build-local. Keep only one to reduce redundancy.

-for artifact in clang-tidy-report.txt clang-tidy-report-full.txt compile_commands.json; do
+for artifact in clang-tidy-report.txt clang-tidy-report-full.txt; do
   if [ -f "$WORKSPACE_COPY/$artifact" ]; then
     cp "$WORKSPACE_COPY/$artifact" "$ROOT_DIR/$artifact"
   fi
 done
-if [ -f "$WORKSPACE_COPY/build-local/compile_commands.json" ]; then
-  cp "$WORKSPACE_COPY/build-local/compile_commands.json" "$ROOT_DIR/compile_commands.json"
-fi
+# Prefer the build-local one if present; otherwise rely on the in-container copy to root
+if [ -f "$WORKSPACE_COPY/build-local/compile_commands.json" ]; then
+  cp "$WORKSPACE_COPY/build-local/compile_commands.json" "$ROOT_DIR/compile_commands.json"
+fi
core/src/adapters/git/libgit2_repository_port.c (1)

800-808: Limit fallback signature to ENOTFOUND only (avoid masking other failures).

If git_signature_default fails for reasons other than missing user config, we should surface the error instead of silently falling back. Recommend checking for GIT_ENOTFOUND explicitly; otherwise return an error.

-    git_signature *sig = NULL;
-    if (git_signature_default(&sig, state->repo) < 0) {
-        /* Fallback to a synthetic signature if repo/user config is missing */
-        if (git_signature_now(&sig, "gitmind", "[email protected]") < 0) {
-            return gm_err_void(
-                GM_ERROR(GM_ERR_UNKNOWN, "unable to create commit signature"));
-        }
-    }
+    git_signature *sig = NULL;
+    int sig_status = git_signature_default(&sig, state->repo);
+    if (sig_status < 0) {
+        if (sig_status == GIT_ENOTFOUND) {
+            /* Fallback to a synthetic signature if repo/user config is missing */
+            if (git_signature_now(&sig, "gitmind", "[email protected]") < 0) {
+                return gm_err_void(
+                    GM_ERROR(GM_ERR_UNKNOWN, "unable to create commit signature"));
+            }
+        } else {
+            return gm_err_void(
+                GM_ERROR(GM_ERR_UNKNOWN, "default commit signature lookup failed"));
+        }
+    }
core/tests/unit/test_repo_build_tree.c (1)

88-98: Strengthen assertions: verify tree contents.

Currently only checks type. Also assert that root.txt and dir exist with expected kinds.

/* After verifying obj is a tree, also validate entries */
git_tree *tree = NULL;
rc = git_tree_lookup(&tree, repo, (const git_oid *)&tree_oid);
assert(rc == 0 && tree);

const git_tree_entry *e_root = git_tree_entry_byname(tree, "root.txt");
assert(e_root && git_tree_entry_type(e_root) == GIT_OBJECT_BLOB);

const git_tree_entry *e_dir = git_tree_entry_byname(tree, "dir");
assert(e_dir && git_tree_entry_type(e_dir) == GIT_OBJECT_TREE);

git_tree_free(tree);
core/include/gitmind/journal/internal/append_plan.h (2)

16-21: Clarify ownership/lifetime and parent semantics in plan struct

Pointers imply borrowed storage, but ownership and lifetime are undocumented. Also, parents + parent_count (0/1) suggests a single optional parent; plural naming may mislead.

Recommend:

  • Document that tree_oid, message, and parents are borrowed and must outlive the plan use; specify NULLability constraints (e.g., parents may be NULL when parent_count == 0; message must be non-NULL).
  • Or make OIDs by-value to avoid lifetime hazards and keep message explicitly borrowed.

Example doc-only tweak:

 typedef struct gm_journal_commit_plan {
-    const gm_oid_t *tree_oid;           /* required */
-    const char *message;                /* required, base64-encoded CBOR */
-    const gm_oid_t *parents;            /* optional pointer to single parent */
-    size_t parent_count;                /* 0 or 1 for now */
+    const gm_oid_t *tree_oid;           /* required, borrowed; non-NULL */
+    const char *message;                /* required, borrowed; base64-encoded CBOR; non-NULL */
+    const gm_oid_t *parents;            /* optional, borrowed; points to single parent when parent_count == 1, NULL otherwise */
+    size_t parent_count;                /* 0 or 1 for now */
 } gm_journal_commit_plan_t;

If you prefer stronger guarantees, switch to by-value OIDs and a bool has_parent to eliminate aliasing risks.


23-28: Parameter naming consistency (empty_tree_oid vs tree_oid)

The struct uses tree_oid, while the API parameter is empty_tree_oid. If this function accepts any tree, rename to tree_oid for clarity.

-GM_NODISCARD gm_result_void_t gm_journal_build_commit_plan(
-    const gm_oid_t *empty_tree_oid,
+GM_NODISCARD gm_result_void_t gm_journal_build_commit_plan(
+    const gm_oid_t *tree_oid,
     const gm_oid_t *parent_oid_opt,
     const char *message,
     gm_journal_commit_plan_t *out_plan);

If it truly must be the empty tree, document that invariant explicitly. Also clarify nullability: tree_oid and message must be non-NULL; parent_oid_opt may be NULL; out_plan must be non-NULL.

core/include/gitmind/journal/internal/read_decoder.h (1)

19-33: Specify out-parameter nullability and success invariants

Please tighten the contract:

  • Allow passing NULL for outputs the caller doesn't need (out_basic, out_attr, consumed, got_attr), and document which are required for each decode path.
  • On success:
    • If got_attr == true, out_attr must be non-NULL and fully populated; out_basic remains untouched.
    • If got_attr == false, out_basic must be non-NULL and fully populated; out_attr remains untouched.
  • Guarantee consumed <= len and define whether consumed is set on failure.

Suggested doc-only update:

 /* Decode a single edge from CBOR bytes.
  * - If prefer_attributed is true, try attributed first and fall back to legacy.
  * - If false, try legacy first and fall back to attributed; when falling back,
  *   the attributed edge is converted into a basic edge.
- * Returns GM_OK on success and sets consumed to bytes read and got_attr when an
- * attributed edge was produced.
+ * Outputs (NULLable):
+ *   - out_basic: populated when producing a basic (legacy) edge; may be NULL if caller doesn't need it.
+ *   - out_attr: populated when producing an attributed edge; may be NULL if caller doesn't need it.
+ *   - consumed: set to bytes read on success; may be NULL.
+ *   - got_attr: set to true when an attributed edge was produced; may be NULL.
+ * Invariants:
+ *   - On success, exactly one of (out_basic, out_attr) is written, matching got_attr.
+ *   - consumed <= len. On failure, consumed is left unchanged.
+ * Returns GM_OK on success.
  */

If NULL outputs are not allowed, mark required ones as non-NULL in comments/macros and define behavior for the unused output.

core/include/gitmind/telemetry/internal/config.h (1)

12-12: Remove unused include.

The header includes gitmind/ports/fs_temp_port.h but none of the declarations in this file reference the fs_temp port types. This include appears to be unnecessary.

Apply this diff to remove the unused include:

 #include "gitmind/ports/env_port.h"
-#include "gitmind/ports/fs_temp_port.h"
 #include "gitmind/ports/logger_port.h"
core/tests/unit/test_cache_oid_prefix.c (1)

12-32: Expand test coverage for bit rounding and error conditions.

The test only validates bit values that are multiples of 4. According to the function's documentation in oid_prefix.h, bits are "rounded up to the next whole hex character (4 bits per char)". Consider adding tests for:

  • Non-multiple-of-4 bit values (e.g., 5, 7, 9 bits) to verify rounding behavior
  • Boundary conditions (0 bits, negative bits, bits > 160)
  • Buffer size edge cases (buffer too small for requested prefix)
  • Error handling paths

Example test additions:

/* Test rounding: 5 bits should round up to 8 bits (2 hex chars) */
assert(gm_cache_oid_prefix(&oid, 5, out, sizeof(out)) == GM_OK);
assert(strcmp(out, "01") == 0);

/* Test boundary: 0 bits */
assert(gm_cache_oid_prefix(&oid, 0, out, sizeof(out)) == GM_OK);
assert(strcmp(out, "") == 0);

/* Test error: NULL oid */
assert(gm_cache_oid_prefix(NULL, 4, out, sizeof(out)) != GM_OK);

/* Test error: NULL output buffer */
assert(gm_cache_oid_prefix(&oid, 4, NULL, sizeof(out)) != GM_OK);
core/tests/unit/test_log_formatter.c (1)

25-40: Strengthen JSON format validation.

The JSON test only verifies the presence of braces and specific substrings, which doesn't guarantee valid JSON. Consider:

  • Parsing the output with a JSON parser to verify structural validity
  • Testing special character escaping (quotes, newlines, backslashes in values)
  • Verifying field ordering or using a more robust JSON comparison
  • Testing edge cases (empty KV array, NULL keys/values)

Example improvements:

/* After rendering, verify it's parseable JSON (if a JSON parser is available) */
/* Or at minimum, check for proper quote escaping */
const gm_log_kv_t kvs_special[] = {
    {.key = "event", .value = "test\"with\"quotes"},
    {.key = "msg", .value = "line1\nline2"},
};
char out_special[256];
assert(gm_log_format_render_default(kvs_special, 2, true, out_special, sizeof(out_special)).ok);
/* Verify escaped quotes and newlines */
assert(strstr(out_special, "\\\"") != NULL);
assert(strstr(out_special, "\\n") != NULL);
core/include/gitmind/cache/internal/oid_prefix.h (1)

15-24: LGTM! Consider documenting edge case behavior.

The header is well-structured. To improve clarity, consider documenting:

  • Why GM_CACHE_MAX_SHARD_PATH is 32 (relationship to SHA-1/SHA-256 hex length, performance considerations, etc.)
  • Behavior when bits exceeds the maximum representable OID bits
  • Thread-safety guarantees of gm_cache_oid_prefix
  • Return value semantics (GM_OK, specific error codes)

Example documentation enhancement:

/*
 * Maximum characters we ever write for an OID shard prefix.
 * 32 chars allows up to 128 bits (32 * 4) of OID prefix for sharding,
 * which is sufficient for SHA-1 (160 bits) and SHA-256 (256 bits).
 */
#define GM_CACHE_MAX_SHARD_PATH 32

/*
 * Compute a hexadecimal OID prefix for sharding. The number of bits is rounded
 * up to the next whole hex character (4 bits per char). The output is
 * NUL-terminated and clamped to GM_CACHE_MAX_SHARD_PATH - 1 characters.
 *
 * Thread-safe: yes (pure function, no global state)
 * @return GM_OK on success, GM_ERR_INVALID_ARGUMENT if oid/out is NULL
 */
core/tests/unit/test_journal_port.c (1)

17-52: Expand test coverage beyond the happy path.

The test validates basic append functionality but lacks coverage for:

  • Error conditions (NULL port, NULL edges, invalid edge data)
  • Multiple edge append in a single call
  • Edge cases (zero edges, extremely large edge counts)
  • Append failures and error propagation
  • Different branch names (currently hardcoded to "main")

Consider adding test functions for error paths:

static void test_append_null_port(void) {
    printf("test_journal_port.append_null_port... ");
    gm_edge_t e = {0};
    gm_result_void_t r = gm_cmd_journal_port_append(NULL, &e, 1);
    assert(!r.ok);
    assert(r.error.code == GM_ERR_INVALID_ARGUMENT);
    printf("OK\n");
}

static void test_append_zero_edges(void) {
    printf("test_journal_port.append_zero_edges... ");
    /* Setup port as in main test... */
    gm_result_void_t r = port.vtbl->append(&port, NULL, 0);
    assert(!r.ok || r.ok); /* Document expected behavior */
    printf("OK\n");
}
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1)

16-34: Consider special character handling in diagnostic output.

The emit_impl function writes keys and values directly to stderr without escaping special characters (newlines, control characters, etc.). This could result in malformed or misleading diagnostic output if keys/values contain such characters.

Consider adding basic escaping for newlines and control characters:

static void emit_escaped(const char *str) {
    if (!str) {
        fputs("(null)", stderr);
        return;
    }
    for (const char *p = str; *p; ++p) {
        if (*p == '\n') {
            fputs("\\n", stderr);
        } else if (*p == '\r') {
            fputs("\\r", stderr);
        } else if (*p == '\t') {
            fputs("\\t", stderr);
        } else {
            fputc(*p, stderr);
        }
    }
}

Then use emit_escaped instead of fputs for component, event, and KV pairs.

core/tests/unit/test_journal_mixed_cbor.c (1)

20-22: Deduplicate includes to reduce churn

Duplicate headers included:

  • gitmind/error.h appears twice (Line 13 and earlier)
  • gitmind/types.h appears twice (Line 21 and earlier)

Safe to remove the later duplicates. Keeps compile units cleaner.

apps/cli/main.c (1)

14-15: Avoid including headers from core/src in apps

Including core/src/... headers couples the CLI to internal paths. Prefer public headers (e.g., move these to core/include and include via gitmind/adapters/...).

core/tests/unit/test_journal_nff_retry.c (2)

84-92: Stale/misleading comment about head resolution

The stub does implement head_branch (Lines 46-50). Update/remove this block to avoid confusion.


80-82: Prefer GM_OK over literal 0 in asserts

Use GM_OK for readability and to avoid coupling to specific numeric values.

core/tests/unit/test_cache_tree_size.c (1)

128-133: Implement guaranteed cleanup in test teardown
File: core/tests/unit/test_cache_tree_size.c:128-133
Assertion failures bypass the current cleanup block, leaving temp trees behind. Move removal and disposal into a teardown hook or register with atexit() (or use your test framework’s fixture teardown) to ensure it always runs.

core/src/domain/cache/oid_prefix.c (1)

22-28: Avoid int overflow; use size_t for counts and add early exit when no chars to emit

Casting out_size to int can overflow on very large buffers, unexpectedly producing 0 chars. Use size_t for all char-count math and return early when chars == 0. Behavior remains identical otherwise.

-    int chars = (bits + (BITS_PER_HEX_CHAR - 1)) / BITS_PER_HEX_CHAR;
-    if (chars > GM_OID_HEX_CHARS) chars = GM_OID_HEX_CHARS;
-    if ((size_t)chars >= out_size) {
-        /* clamp to buffer, still try to fill */
-        chars = (int)out_size - 1;
-        if (chars < 0) chars = 0;
-    }
+    size_t chars = (size_t)((bits + (BITS_PER_HEX_CHAR - 1)) / BITS_PER_HEX_CHAR);
+    if (chars > (size_t)GM_OID_HEX_CHARS) chars = (size_t)GM_OID_HEX_CHARS;
+    if (chars >= out_size) {
+        /* clamp to buffer, still try to fill */
+        chars = out_size - 1;
+    }
@@
-    if (chars > (GM_CACHE_MAX_SHARD_PATH - 1)) {
-        chars = GM_CACHE_MAX_SHARD_PATH - 1;
-    }
+    if (chars > (size_t)(GM_CACHE_MAX_SHARD_PATH - 1)) {
+        chars = (size_t)(GM_CACHE_MAX_SHARD_PATH - 1);
+    }
+    if (chars == 0) {
+        return GM_OK;
+    }
@@
-    for (int i = 0; i < chars; ++i) {
+    for (size_t i = 0; i < chars; ++i) {
         out[i] = hex[i];
     }
     out[chars] = '\0';

Please confirm unit tests cover very large out_size values to prevent regressions.

Also applies to: 34-37, 39-43

core/src/adapters/logging/stdio_logger_adapter.c (2)

4-4: Ensure stdio types are declared

This file uses FILE and fprintf. If stdio_logger_adapter.h does not include <stdio.h>, compilation will fail here.

 #include "stdio_logger_adapter.h"
+#include <stdio.h>
 #include "gitmind/error.h"
 #include "gitmind/result.h"

If the header already includes <stdio.h>, this remains harmless. Please confirm.


35-41: Make UTC time formatting portable (Windows vs POSIX)

gmtime_r isn’t available on MSVC. Add a guarded fallback to gmtime_s (Windows) to keep builds portable.

-    struct tm tm;
-    gmtime_r(&now, &tm);
+    struct tm tm;
+#if defined(_WIN32)
+    gmtime_s(&tm, &now);
+#else
+    gmtime_r(&now, &tm);
+#endif

Optional: if logs can be emitted concurrently, consider a simple mutex around fprintf to avoid interleaving lines.

apps/cli/main.md (2)

92-95: Clarify accepted values for GITMIND_SAFETY

Specify all accepted values (e.g., off/0/false vs on/1/true) and default behavior to avoid ambiguity in CI/scripts.


253-261: Add a quick JSON logging usage example

A one-liner improves discoverability. Example:

GITMIND_LOG_FORMAT=json git-mind list 2> logs.json && jq . < logs.json

Also applies to: 262-281

core/tests/fakes/logging/fake_logger_port.c (1)

13-25: Test fake looks solid

Captures entries safely and disposes correctly. Good test double.

Optional: expose the 32-entry capacity as a public constant in the fake header to avoid magic numbers in tests.

Also applies to: 31-46, 48-53

core/tests/unit/test_diagnostics_port.c (2)

20-29: Guard against zero-sized buffers in sr_repo_path; prefer snprintf for safety

If out_size == 0, writing out[i] = '\0' is UB. Simplify and harden the copy.

Apply:

 static gm_result_void_t sr_repo_path(void *self, gm_git_repository_path_kind_t kind,
                                      char *out, size_t out_size) {
     (void)self; (void)kind;
-    /* Any path; fake fs will report not found for canonicalize in prep, but we
-     * want tree-build failure path in commit stage; use a valid-ish path. */
+    /* Use a valid-ish path; fake FS canonicalization will fail during prep. */
     const char *p = "/fake/state/.git";
-    size_t i = 0; while (p[i] && i + 1 < out_size) { out[i] = p[i]; ++i; }
-    out[i] = '\0';
-    return gm_ok_void();
+    if (out_size == 0) {
+        return gm_err_void(GM_ERROR(GM_ERR_INVALID_ARGUMENT, "buffer too small"));
+    }
+    (void)snprintf(out, out_size, "%s", p);
+    return gm_ok_void();
 }

17-18: Align comments with actual failure path

Top comment says “fail tree build,” but the execution path intentionally fails during prep via fake FS. Update wording for consistency.

Also applies to: 57-59

core/tests/unit/test_telemetry_cfg.c (2)

8-11: Make set_env portable (_putenv on Windows) and keep heap lifetime

On Windows, putenv is _putenv; current code will likely fail to build. Wrap per-platform.

Apply:

-#ifndef _WIN32
-extern int putenv(char *);
-#endif
+#ifdef _WIN32
+extern int _putenv(const char *);
+#else
+extern int putenv(char *);
+#endif
@@
 static void set_env(const char *k, const char *v) {
     /* Portable: use putenv with a heap string that must persist */
     char buf[256];
     if (v == NULL) v = ""; /* empty resets to default behavior */
     int n = snprintf(buf, sizeof(buf), "%s=%s", k, v);
     assert(n > 0 && (size_t)n < sizeof(buf));
     char *heap = (char *)malloc((size_t)n + 1);
     assert(heap != NULL);
     memcpy(heap, buf, (size_t)n + 1);
-    assert(putenv(heap) == 0);
+    /* do not free(heap); it must persist after putenv/_putenv */
+#ifdef _WIN32
+    assert(_putenv(heap) == 0);
+#else
+    assert(putenv(heap) == 0);
+#endif
 }

Also applies to: 14-24


43-48: Loosen exact tag ordering to reduce brittleness

Order could evolve; assert presence instead of exact equality.

Apply:

-    assert(strcmp(tags, "branch=main,mode=full") == 0);
+    assert(strstr(tags, "branch=main") != NULL);
+    assert(strstr(tags, "mode=full") != NULL);
core/tests/fakes/diagnostics/fake_diagnostics_port.h (1)

15-21: Replace magic numbers with named constants for clarity and reuse

Introduce size macros to document limits and ease future changes.

Apply:

+/* Tunable limits for the fake diagnostics sink */
+#define GM_FAKE_DIAG_MAX_EVENTS 64
+#define GM_FAKE_DIAG_MAX_KVS_PER_EVENT 8
+#define GM_FAKE_DIAG_MAX_COMPONENT 32
+#define GM_FAKE_DIAG_MAX_EVENT 64
+#define GM_FAKE_DIAG_MAX_KEY 32
+#define GM_FAKE_DIAG_MAX_VALUE 64
+
 typedef struct {
-    struct { char component[32]; char event[64]; } meta[64];
-    struct { char key[32]; char value[64]; } kvs[64][8];
-    size_t kv_counts[64];
+    struct { char component[GM_FAKE_DIAG_MAX_COMPONENT]; char event[GM_FAKE_DIAG_MAX_EVENT]; } meta[GM_FAKE_DIAG_MAX_EVENTS];
+    struct { char key[GM_FAKE_DIAG_MAX_KEY]; char value[GM_FAKE_DIAG_MAX_VALUE]; } kvs[GM_FAKE_DIAG_MAX_EVENTS][GM_FAKE_DIAG_MAX_KVS_PER_EVENT];
+    size_t kv_counts[GM_FAKE_DIAG_MAX_EVENTS];
     size_t count;
 } gm_fake_diag_state_t;
core/src/ports/cache/cache_query_port.c (1)

16-31: Zero/initialize outputs on failure paths for safety.

Consider clearing gm_cache_result_t on entry and/or before returning errors so callers never read garbage on failures.

Example pattern:

  • memset(out, 0, sizeof(*out)) before delegating
  • Ensure out remains zeroed when returning gm_err_void

As per coding guidelines

Also applies to: 33-48

core/src/domain/journal/codec.c (1)

22-31: Confirm base64 variant choice (ORIGINAL vs URL-safe).

You’re using sodium_base64_VARIANT_ORIGINAL. If the transport requires URL-safe strings, switch to sodium_base64_VARIANT_URLSAFE[_NO_PADDING] for both encode and decode.

If URL-safe is desired:

-    const int variant = sodium_base64_VARIANT_ORIGINAL;
+    const int variant = sodium_base64_VARIANT_URLSAFE_NO_PADDING;

Apply the same variant in gm_journal_decode_message. As per coding guidelines

Also applies to: 39-54

core/tests/unit/test_journal_e2e_libgit2.c (1)

46-55: Use GM_PATH_MAX for paths and check IO results.

Replace fixed-size 1024 buffers with GM_PATH_MAX and verify fwrite returns expected bytes.

-    char a_path[1024];
-    char b_path[1024];
+    char a_path[GM_PATH_MAX];
+    char b_path[GM_PATH_MAX];

And in write_file:

-    size_t n = fwrite(content, 1, strlen(content), f);
-    (void)n;
+    size_t n = fwrite(content, 1, strlen(content), f);
+    assert(n == strlen(content));

As per coding guidelines

core/src/domain/journal/read_decoder.c (1)

13-45: Harden conversion and simplify with gm_ helpers*

Zero-init the destination and use safe copy helpers to avoid manual loops/strlen assumptions and to keep consistency with convert_attributed_to_basic.

-static void convert_legacy_to_attributed(const gm_edge_t *legacy,
-                                         gm_edge_attributed_t *attributed) {
-    for (size_t i = 0; i < GM_SHA1_SIZE; i++) {
-        attributed->src_sha[i] = legacy->src_sha[i];
-        attributed->tgt_sha[i] = legacy->tgt_sha[i];
-    }
-    attributed->src_oid = legacy->src_oid;
-    attributed->tgt_oid = legacy->tgt_oid;
-    attributed->rel_type = legacy->rel_type;
-    attributed->confidence = legacy->confidence;
-    attributed->timestamp = legacy->timestamp;
-    size_t src_len = strlen(legacy->src_path);
-    if (src_len >= sizeof(attributed->src_path)) src_len = sizeof(attributed->src_path) - 1U;
-    for (size_t i = 0; i < src_len; i++) { attributed->src_path[i] = legacy->src_path[i]; }
-    attributed->src_path[src_len] = '\0';
-    size_t tgt_len = strlen(legacy->tgt_path);
-    if (tgt_len >= sizeof(attributed->tgt_path)) tgt_len = sizeof(attributed->tgt_path) - 1U;
-    for (size_t i = 0; i < tgt_len; i++) { attributed->tgt_path[i] = legacy->tgt_path[i]; }
-    attributed->tgt_path[tgt_len] = '\0';
-    size_t ulid_len = strlen(legacy->ulid);
-    if (ulid_len >= sizeof(attributed->ulid)) ulid_len = sizeof(attributed->ulid) - 1U;
-    for (size_t i = 0; i < ulid_len; i++) { attributed->ulid[i] = legacy->ulid[i]; }
-    attributed->ulid[ulid_len] = '\0';
-    attributed->attribution.source_type = GM_SOURCE_HUMAN;
-    attributed->attribution.author[0] = '\0';
-    attributed->attribution.session_id[0] = '\0';
-    attributed->attribution.flags = 0;
-    attributed->lane = GM_LANE_DEFAULT;
-}
+static void convert_legacy_to_attributed(const gm_edge_t *legacy,
+                                         gm_edge_attributed_t *attributed) {
+    gm_memset_safe(attributed, sizeof(*attributed), 0, sizeof(*attributed));
+    (void)gm_memcpy_span(attributed->src_sha, GM_SHA1_SIZE, legacy->src_sha, GM_SHA1_SIZE);
+    (void)gm_memcpy_span(attributed->tgt_sha, GM_SHA1_SIZE, legacy->tgt_sha, GM_SHA1_SIZE);
+    attributed->src_oid = legacy->src_oid;
+    attributed->tgt_oid = legacy->tgt_oid;
+    attributed->rel_type = legacy->rel_type;
+    attributed->confidence = legacy->confidence;
+    attributed->timestamp = legacy->timestamp;
+    (void)gm_strcpy_safe(attributed->src_path, sizeof attributed->src_path, legacy->src_path);
+    (void)gm_strcpy_safe(attributed->tgt_path, sizeof attributed->tgt_path, legacy->tgt_path);
+    (void)gm_strcpy_safe(attributed->ulid, sizeof attributed->ulid, legacy->ulid);
+    attributed->attribution.source_type = GM_SOURCE_HUMAN;
+    attributed->attribution.flags = 0;
+    attributed->lane = GM_LANE_DEFAULT;
+}
core/src/journal/reader.c (2)

29-41: Minor: duplicate include

gitmind/constants_internal.h is included twice (Line 10 and Line 39). Drop the second include.


325-352: Prefer monotonic wall-clock for durations

clock() measures CPU time; journal read is I/O bound. Use a monotonic wall-clock (e.g., clock_gettime(CLOCK_MONOTONIC)/timespec_get or platform abstraction) for accurate durations.

- clock_t st = clock();
- int rc_walk = walk_journal_generic(&rctx, ref_name);
- uint64_t dur_ms = (uint64_t)((clock() - st) / CLOCKS_PER_MS);
+ gm_timepoint_t st = gm_time_now_monotonic();
+ int rc_walk = walk_journal_generic(&rctx, ref_name);
+ uint64_t dur_ms = gm_time_elapsed_ms(st, gm_time_now_monotonic());
core/src/app/cache/cache_rebuild_service.c (2)

175-179: Propagate underlying error from gm_cache_oid_prefix for better diagnostics

Return the exact error from gm_cache_oid_prefix instead of GM_ERR_INVALID_STATE.

-    if (gm_cache_oid_prefix(oid, ctx->shard_bits, prefix, sizeof(prefix)) !=
-        GM_OK) {
-        return GM_ERR_INVALID_STATE;
-    }
+    int pcode = gm_cache_oid_prefix(oid, ctx->shard_bits, prefix, sizeof(prefix));
+    if (pcode != GM_OK) {
+        return pcode;
+    }

346-350: Remove duplicate error check after build_tree_from_temp

The second result_code != GM_OK check is redundant and unreachable.

     int result_code =
         build_tree_from_temp(port, inputs->temp_dir->path, &tree_oid);
-    if (result_code != GM_OK) {
-        /* Emit via diagnostics port when available. We don't have ctx here, so skip. */
-        return result_code;
-    }
-    if (result_code != GM_OK) {
-        return result_code;
-    }
+    if (result_code != GM_OK) {
+        /* Emit via diagnostics port when available. We don't have ctx here, so skip. */
+        return result_code;
+    }

Also applies to: 357-361

core/src/telemetry/config.c (2)

23-61: Use a portable case-insensitive compare wrapper

Direct strcasecmp ties you to POSIX; Windows builds will fail. Prefer a project-provided wrapper (e.g., gm_strcasecmp/gm_ascii_strcasecmp) if available, or add a small shim.

Please confirm the available string-compare helper in gitmind/security/string.h and switch to it for parse_bool_default_true, parse_repo_tag_mode, parse_log_level, parse_log_format, and parse_hash_algo_sha256.


251-287: Handle long repo paths in PLAIN mode (avoid silent drop)

repo_val[65] truncates or drops tags for long canonical paths. Either size this buffer generously or fall back to HASH when copy fails.

For example:

  • Enlarge repo_val (e.g., 256 or GM_PATH_MAX if available), or
  • Check gm_strcpy_safe result; if not GM_OK, compute a short hash instead so the repo tag is still emitted.
core/src/journal/writer.c (4)

321-333: Confirm ownership of repo_canon; free if allocated

gm_fs_temp_port_canonicalize_ex likely allocates repo_canon. It’s never freed here, which would leak per append call.

If caller-owns, free it after tag building (e.g., gm_fs_temp_port_free(ctx->fs_temp_port, repo_canon) or equivalent).


39-41: Use a monotonic wall-clock for duration

clock() measures CPU time on many platforms; it’s not suitable for elapsed wall-clock. Prefer a monotonic clock (e.g., clock_gettime(CLOCK_MONOTONIC) or a project helper).

Example sketch:

  • Capture t0 = monotonic_now_ms();
  • Capture t1 = monotonic_now_ms();
  • dur_ms = t1 - t0;

Also applies to: 364-370


157-168: Check whether commit plan needs disposal

If gm_journal_build_commit_plan allocates (e.g., parents/message buffers), ensure plan resources are released after commit, beyond freeing message.

If an explicit disposer exists (e.g., gm_journal_commit_plan_dispose(&plan)), call it after commit_result handling.


200-253: Note: potential unreachable commit on ref update race

On ALREADY_EXISTS retry, a previous commit object remains unreachable. Likely acceptable, but if you want to avoid object buildup, consider a CAS-style reference update (expected OID) to fail fast before creating the commit.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 836ad12 and 607a20b.

📒 Files selected for processing (106)
  • .github/workflows/auto-seed-review.yml (0 hunks)
  • .gitignore (1 hunks)
  • AGENTS.md (5 hunks)
  • README.md (2 hunks)
  • apps/cli/README.md (1 hunks)
  • apps/cli/Scripting_Patterns.md (1 hunks)
  • apps/cli/link.md (1 hunks)
  • apps/cli/list.md (1 hunks)
  • apps/cli/main.c (4 hunks)
  • apps/cli/main.md (2 hunks)
  • core/include/gitmind/cache/internal/oid_prefix.h (1 hunks)
  • core/include/gitmind/cache/internal/staleness.h (1 hunks)
  • core/include/gitmind/context.h (2 hunks)
  • core/include/gitmind/journal/internal/append_plan.h (1 hunks)
  • core/include/gitmind/journal/internal/codec.h (1 hunks)
  • core/include/gitmind/journal/internal/read_decoder.h (1 hunks)
  • core/include/gitmind/ports/cache_build_port.h (1 hunks)
  • core/include/gitmind/ports/cache_query_port.h (1 hunks)
  • core/include/gitmind/ports/diagnostic_port.h (1 hunks)
  • core/include/gitmind/ports/journal_command_port.h (1 hunks)
  • core/include/gitmind/ports/logger_port.h (1 hunks)
  • core/include/gitmind/ports/metrics_port.h (1 hunks)
  • core/include/gitmind/telemetry/internal/config.h (1 hunks)
  • core/include/gitmind/telemetry/internal/log_format.h (1 hunks)
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1 hunks)
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.h (1 hunks)
  • core/src/adapters/git/libgit2_repository_port.c (3 hunks)
  • core/src/adapters/logging/stdio_logger_adapter.c (1 hunks)
  • core/src/adapters/logging/stdio_logger_adapter.h (1 hunks)
  • core/src/adapters/metrics/null_metrics_adapter.c (1 hunks)
  • core/src/adapters/metrics/null_metrics_adapter.h (1 hunks)
  • core/src/app/cache/cache_rebuild_service.c (10 hunks)
  • core/src/cache/query.c (2 hunks)
  • core/src/domain/cache/oid_prefix.c (1 hunks)
  • core/src/domain/journal/append_planner.c (1 hunks)
  • core/src/domain/journal/codec.c (1 hunks)
  • core/src/domain/journal/read_decoder.c (1 hunks)
  • core/src/journal/reader.c (9 hunks)
  • core/src/journal/writer.c (11 hunks)
  • core/src/ports/cache/cache_build_port.c (1 hunks)
  • core/src/ports/cache/cache_query_port.c (1 hunks)
  • core/src/ports/journal/journal_command_port.c (1 hunks)
  • core/src/telemetry/config.c (1 hunks)
  • core/src/telemetry/log_format.c (1 hunks)
  • core/tests/fakes/diagnostics/fake_diagnostics_port.c (1 hunks)
  • core/tests/fakes/diagnostics/fake_diagnostics_port.h (1 hunks)
  • core/tests/fakes/git/fake_git_repository_port.c (1 hunks)
  • core/tests/fakes/logging/fake_logger_port.c (1 hunks)
  • core/tests/fakes/logging/fake_logger_port.h (1 hunks)
  • core/tests/fakes/metrics/fake_metrics_port.c (1 hunks)
  • core/tests/fakes/metrics/fake_metrics_port.h (1 hunks)
  • core/tests/unit/support/temp_repo_helpers.h (1 hunks)
  • core/tests/unit/test_cache_branch_limits.c (3 hunks)
  • core/tests/unit/test_cache_meta.c (3 hunks)
  • core/tests/unit/test_cache_oid_prefix.c (1 hunks)
  • core/tests/unit/test_cache_query.c (3 hunks)
  • core/tests/unit/test_cache_telemetry_emit.c (1 hunks)
  • core/tests/unit/test_cache_tree_size.c (3 hunks)
  • core/tests/unit/test_cli_json_env.c (1 hunks)
  • core/tests/unit/test_diagnostics_port.c (1 hunks)
  • core/tests/unit/test_journal_e2e_libgit2.c (1 hunks)
  • core/tests/unit/test_journal_mixed_cbor.c (3 hunks)
  • core/tests/unit/test_journal_nff_retry.c (1 hunks)
  • core/tests/unit/test_journal_port.c (1 hunks)
  • core/tests/unit/test_journal_port_append_flow.c (1 hunks)
  • core/tests/unit/test_log_formatter.c (1 hunks)
  • core/tests/unit/test_repo_build_tree.c (1 hunks)
  • core/tests/unit/test_telemetry_cfg.c (1 hunks)
  • docs/PRDs/PRD-co-thought-mcp-service.md (1 hunks)
  • docs/PRDs/PRD-git-mind-semantics-time-travel-prototype.md (1 hunks)
  • docs/README.md (1 hunks)
  • docs/architecture/attribution-integration-guide.md (0 hunks)
  • docs/architecture/attribution-system.md (0 hunks)
  • docs/architecture/attribution-use-cases.md (0 hunks)
  • docs/architecture/augments.md (1 hunks)
  • docs/architecture/cache-architecture.md (1 hunks)
  • docs/architecture/hexagonal/Journal.md (1 hunks)
  • docs/architecture/journal-architecture.md (1 hunks)
  • docs/architecture/ref-name-validation.md (1 hunks)
  • docs/architecture/system-overview.md (1 hunks)
  • docs/cli/README.md (0 hunks)
  • docs/cli/combined_markdown.md (0 hunks)
  • docs/cli/gitmind-cache-rebuild.md (0 hunks)
  • docs/cli/gitmind-install-hooks.md (0 hunks)
  • docs/cli/gitmind-link.md (0 hunks)
  • docs/cli/gitmind-list.md (0 hunks)
  • docs/cli/gitmind.md (0 hunks)
  • docs/code-reviews/PR173/17ddad1aa3747f40ecacc0bd202e7b83edbf1d1d.md (0 hunks)
  • docs/code-reviews/PR173/7445bcf00c71b6f768e9eb6b1fd99d2b1c60783f.md (0 hunks)
  • docs/code-reviews/PR173/e0243058aac14ff3a8c83af2f6d4dcfe97dd2804.md (0 hunks)
  • docs/code-reviews/PR178/1a49c6f455c33f1dcf87905f804020b6654be938.md (3 hunks)
  • docs/code-reviews/PR178/9b1f53d013ae6aa4f30edf4dea8dc4ddf7de16bc.md (1 hunks)
  • docs/code-reviews/PR178/a39bfee82cb78194ff33fc588d43321c98d9cc8c.md (3 hunks)
  • docs/code-reviews/PR178/e1215ee158d04aca9718a9e775212be3b70a15f8.md (3 hunks)
  • docs/operations/Diagnostics_Events.md (1 hunks)
  • docs/operations/Observability.md (1 hunks)
  • docs/operations/Telemetry_Config.md (1 hunks)
  • docs/planning/Milestones.md (1 hunks)
  • docs/planning/Product_Roadmap.md (1 hunks)
  • docs/planning/Release_Plans.md (1 hunks)
  • docs/planning/Sprint_Plans.md (1 hunks)
  • docs/specs/Technical_Specifications.md (1 hunks)
  • docs/wish-list-features/README.md (1 hunks)
  • meson.build (6 hunks)
  • migration-progress.md (1 hunks)
  • tools/ci/ci_local.sh (3 hunks)
💤 Files with no reviewable changes (14)
  • docs/architecture/attribution-system.md
  • docs/code-reviews/PR173/e0243058aac14ff3a8c83af2f6d4dcfe97dd2804.md
  • .github/workflows/auto-seed-review.yml
  • docs/cli/gitmind-list.md
  • docs/cli/gitmind-cache-rebuild.md
  • docs/architecture/attribution-use-cases.md
  • docs/cli/README.md
  • docs/cli/combined_markdown.md
  • docs/code-reviews/PR173/7445bcf00c71b6f768e9eb6b1fd99d2b1c60783f.md
  • docs/architecture/attribution-integration-guide.md
  • docs/code-reviews/PR173/17ddad1aa3747f40ecacc0bd202e7b83edbf1d1d.md
  • docs/cli/gitmind.md
  • docs/cli/gitmind-link.md
  • docs/cli/gitmind-install-hooks.md
🧰 Additional context used
🧠 Learnings (19)
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to core/src/**/{hooks,cache,journal}/**/*.{c,h} : Avoid direct libgit2 usage; route repository/ref operations through gm_git_repository_port/gm_git_ref_port adapters

Applied to files:

  • core/tests/unit/test_cache_query.c
  • apps/cli/main.c
  • core/src/adapters/git/libgit2_repository_port.c
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to {core,src,apps,tests}/**/*.{c,h} : Prefer OID types and helpers (gm_oid_t, git_oid_cmp via wrappers, gm_oid_to_hex); avoid direct libgit2 helpers in domain/application layers

Applied to files:

  • core/include/gitmind/cache/internal/oid_prefix.h
  • core/src/app/cache/cache_rebuild_service.c
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to .coderabbit.yml : Maintain CodeRabbit config for summary-first reviews and limited per-line comments on docs

Applied to files:

  • docs/code-reviews/PR178/a39bfee82cb78194ff33fc588d43321c98d9cc8c.md
  • docs/code-reviews/PR178/e1215ee158d04aca9718a9e775212be3b70a15f8.md
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to docs/architecture/Ref_Name_Validation.md : Document and adhere to ref-name validation policy (reject leading refs/; normalize via libgit2) as specified

Applied to files:

  • docs/architecture/ref-name-validation.md
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to {core,src,apps,tests}/**/*.{c,h} : Naming: functions/vars lower_snake_case with gm_ prefix; macros UPPER_SNAKE; types end with _t; header guards start with GITMIND_

Applied to files:

  • core/src/journal/writer.c
  • AGENTS.md
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to {core,src,apps,tests}/**/*.{h} : Header guards must be stable and named GITMIND_*; add extern "C" guards for C++ consumers in public headers

Applied to files:

  • AGENTS.md
📚 Learning: 2025-09-13T03:13:54.145Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-09-13T03:13:54.145Z
Learning: Applies to {core,include}/**/*.{c,h} : Language: C23 with warnings-as-errors; no VLAs; no variable shadowing; use explicit prototypes

Applied to files:

  • AGENTS.md
📚 Learning: 2025-09-13T03:13:54.145Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-09-13T03:13:54.145Z
Learning: Applies to {include,core/include}/**/*.h : Header guards use GITMIND_* naming

Applied to files:

  • AGENTS.md
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to {core,src,apps,tests}/**/*.{c,h} : Language: C23 with warnings-as-errors; no VLAs, no shadowing; declare explicit prototypes

Applied to files:

  • AGENTS.md
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to include/**/*.h : Umbrella API headers (include/gitmind.h and include/gitmind/**) must be umbrella-safe and carry extern "C" guards for C++

Applied to files:

  • AGENTS.md
📚 Learning: 2025-09-13T03:13:54.145Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-09-13T03:13:54.145Z
Learning: Applies to include/gitmind.h : Maintain umbrella API at include/gitmind.h

Applied to files:

  • AGENTS.md
  • core/include/gitmind/context.h
  • meson.build
📚 Learning: 2025-09-13T03:13:54.145Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-09-13T03:13:54.145Z
Learning: Applies to {core,include}/**/*.{c,h} : Formatting: follow .clang-format (LLVM-based, 4 spaces, 80 cols, pointer alignment right); pre-commit enforces clang-format

Applied to files:

  • AGENTS.md
📚 Learning: 2025-09-13T03:13:54.145Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-09-13T03:13:54.145Z
Learning: Applies to include/gitmind/**/*.h : Public, namespaced headers live under include/gitmind/

Applied to files:

  • AGENTS.md
  • meson.build
📚 Learning: 2025-09-14T09:27:33.283Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-14T09:27:33.283Z
Learning: Applies to **/*.{c,cc,cpp,cxx,h,hpp,hxx,m,mm} : NEVER use `NOLINT` to suppress clang-tidy warnings; ALWAYS fix the underlying issue

Applied to files:

  • AGENTS.md
📚 Learning: 2025-09-13T03:13:54.145Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-09-13T03:13:54.145Z
Learning: Conventional commits (type(scope): description) and reference issues; PRs must describe changes, link issues, include test plan, and update docs when applicable

Applied to files:

  • AGENTS.md
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to {core,src,apps,tests}/**/*.{c,h} : Maintain one responsibility per translation unit where practical; split multi-concern files when touched

Applied to files:

  • AGENTS.md
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to docs/activity/**/*.md : Document migration progress and lessons under docs/activity/<date>_hexagonal.md

Applied to files:

  • migration-progress.md
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to docs/architecture/hexagonal/** : Track adapter status and migrations in docs/architecture/hexagonal/**; keep diagrams and status tables updated

Applied to files:

  • migration-progress.md
📚 Learning: 2025-10-01T04:13:25.799Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-01T04:13:25.799Z
Learning: Applies to core/include/gitmind/ports/**/*.h : Define all inbound/outbound ports as headers only under core/include/gitmind/ports/** with POD structs and function-pointer vtables (no non-trivial inline behavior)

Applied to files:

  • core/include/gitmind/context.h
  • meson.build

@flyingrobots
Copy link
Member Author

Added debug-container workflow tooling and canonicalize regression coverage. Latest run: make ci-local.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (2)
tools/ci/ci_local.sh (1)

31-41: Require explicit host-build override instead of auto-fallback

To align with the “Docker-only unless explicitly overridden” policy, fail when Docker is absent unless GITMIND_ALLOW_HOST_BUILD=1 is set by the user, rather than exporting it automatically.

Based on learnings

-if ! command -v docker >/dev/null 2>&1; then
-  echo "⚠️ Docker not found. Falling back to host build (override guard)."
-  export GITMIND_ALLOW_HOST_BUILD=1
+if ! command -v docker >/dev/null 2>&1; then
+  if [ "${GITMIND_ALLOW_HOST_BUILD:-}" != "1" ]; then
+    echo "❌ Docker not found. Set GITMIND_ALLOW_HOST_BUILD=1 to run a host build at your own risk."
+    exit 1
+  fi
+  echo "⚠️ Docker not found. Proceeding with host build (explicit override)."
   CC=${CC:-clang}
   echo "==> Host build with $CC"
   rm -rf build-local
   CC=$CC meson setup build-local
   ninja -C build-local
   ninja -C build-local test
   exit 0
 fi
core/src/telemetry/config.c (1)

272-291: Dispose the crypto context before returning

gm_crypto_context_create hands back a context that must be released. sha256_hex12 exits on every path without calling gm_crypto_context_dispose, so each invocation leaks the backend resources. Please dispose the context whether the hash succeeds or we fall back to FNV.

 static void sha256_hex12(const uint8_t *data, size_t len, char *out12) {
     uint8_t digest[GM_SHA256_DIGEST_SIZE];
     gm_result_crypto_context_t cr = gm_crypto_context_create(gm_crypto_backend_libsodium());
     if (!cr.ok) {
         /* Fallback to FNV on any error */
         fnv1a64_hex12(data, len, out12);
         return;
     }
-    gm_result_void_t hr = gm_sha256_with_context(&cr.u.val, data, len, digest);
+    gm_result_void_t hr = gm_sha256_with_context(&cr.u.val, data, len, digest);
     if (!hr.ok) {
         if (hr.u.err) gm_error_free(hr.u.err);
+        gm_crypto_context_dispose(&cr.u.val);
         fnv1a64_hex12(data, len, out12);
         return;
     }
+    gm_crypto_context_dispose(&cr.u.val);
     static const char HEX[] = "0123456789abcdef";
     for (int i = 0; i < 6; ++i) {
         out12[2 * i] = HEX[(digest[i] >> 4) & 0xF];
         out12[2 * i + 1] = HEX[digest[i] & 0xF];
     }
     out12[12] = '\0';
 }
🧹 Nitpick comments (12)
tools/ci/ci_local.sh (1)

72-88: Rsync filter: ensure required CI bits can’t be lost to .gitignore

If any needed paths (e.g., .ci/, quality/) are ignored locally, the rsync will drop them. Consider explicit includes as a guard.

 rsync -a --delete \
   --include '/core/' \
   --include '/core/***' \
+  --include '/.ci/' \
+  --include '/.ci/***' \
+  --include '/quality/' \
+  --include '/quality/***' \
   --filter=':- .gitignore' \
   --exclude '/build-local/' \
   --exclude '/ci_logs.zip' \
   --exclude '/clang-tidy-report.txt' \
   --exclude '/clang-tidy-report-full.txt' \
   "$ROOT_DIR"/ "$WORKSPACE_COPY"/
core/src/domain/journal/read_decoder.c (1)

45-48: Use sizeof(field) for consistency

Prefer sizeof(basic->src_path)/sizeof(basic->ulid) over GM_PATH_MAX/GM_ULID_SIZE+1 to avoid drift if struct definitions change.

-    (void)gm_strcpy_safe(basic->src_path, GM_PATH_MAX, aedge->src_path);
-    (void)gm_strcpy_safe(basic->tgt_path, GM_PATH_MAX, aedge->tgt_path);
-    (void)gm_strcpy_safe(basic->ulid, GM_ULID_SIZE + 1, aedge->ulid);
+    (void)gm_strcpy_safe(basic->src_path, sizeof(basic->src_path), aedge->src_path);
+    (void)gm_strcpy_safe(basic->tgt_path, sizeof(basic->tgt_path), aedge->tgt_path);
+    (void)gm_strcpy_safe(basic->ulid, sizeof(basic->ulid), aedge->ulid);
tools/debug/debug-container.sh (2)

66-81: Handle absence of rsync (tar fallback like ci_local.sh)

Mirror the tar-based fallback to avoid hard dependency on rsync.

-if [[ ${RESYNC} -eq 1 ]]; then
+if [[ ${RESYNC} -eq 1 ]]; then
   echo "==> Syncing workspace into ${STAGE_DIR}" >&2
-  rsync -a --delete \
-    --filter=':- .gitignore' \
-    --exclude '/build/' \
-    --exclude '/build-asan/' \
-    --exclude '/build-debug/' \
-    --exclude '/build-local/' \
-    --exclude '/ci_logs.zip' \
-    --exclude '/clang-tidy-report.txt' \
-    --exclude '/clang-tidy-report-full.txt' \
-    "${ROOT_DIR}/" "${STAGE_DIR}/"
+  if command -v rsync >/dev/null 2>&1; then
+    rsync -a --delete \
+      --filter=':- .gitignore' \
+      --exclude '/build/' \
+      --exclude '/build-asan/' \
+      --exclude '/build-debug/' \
+      --exclude '/build-local/' \
+      --exclude '/ci_logs.zip' \
+      --exclude '/clang-tidy-report.txt' \
+      --exclude '/clang-tidy-report-full.txt' \
+      "${ROOT_DIR}/" "${STAGE_DIR}/"
+  else
+    (cd "${ROOT_DIR}" && tar -cf - . \
+        --exclude='./build' \
+        --exclude='./build-asan' \
+        --exclude='./build-debug' \
+        --exclude='./build-local' \
+        --exclude='./ci_logs.zip' \
+        --exclude='./clang-tidy-report.txt' \
+        --exclude='./clang-tidy-report-full.txt') | (cd "${STAGE_DIR}" && tar -xf -)
+  fi

90-98: Sanitize/quote package list to avoid command injection

INSTALL_PKGS is interpolated into a shell command. Quote or validate tokens (alnum, dash, plus) before composing CONTAINER_ENTRY.

-  INSTALL_SNIPPET="apt-get update >/dev/null && apt-get install -y --no-install-recommends ${INSTALL_PKGS} >/dev/null && "
+  # split and safely quote packages
+  read -r -a PKG_ARR <<<"${INSTALL_PKGS}"
+  SAFE_PKGS=()
+  for p in "${PKG_ARR[@]}"; do
+    if [[ "${p}" =~ ^[A-Za-z0-9.+-]+$ ]]; then
+      SAFE_PKGS+=("${p}")
+    else
+      echo "Invalid package name: ${p}" >&2; exit 1
+    fi
+  done
+  INSTALL_SNIPPET="apt-get update >/dev/null && apt-get install -y --no-install-recommends ${SAFE_PKGS[*]} >/dev/null && "
core/tests/support/temp_repo_helpers.c (2)

47-79: Windows path separators not handled in mkdir recursion

Parent derivation uses '/' only. On Windows, paths use backslashes. Consider using a cross-platform path helper or checking both separators.

-        char *slash = strrchr(parent, '/');
+        char *slash = strrchr(parent, '/');
+#ifdef _WIN32
+        char *bslash = strrchr(parent, '\\');
+        if (bslash != NULL && (slash == NULL || bslash > slash)) slash = bslash;
+#endif

219-231: Cleanup staged temp dir on template copy failure

If gm_test_copy_tree fails, the created temp dir remains. Clean it up to avoid clutter.

-            gm_result_void_t copy_result = gm_test_copy_tree(template_path, out_path);
-            if (!copy_result.ok) {
-                return copy_result;
-            }
+            gm_result_void_t copy_result = gm_test_copy_tree(template_path, out_path);
+            if (!copy_result.ok) {
+                (void)gm_fs_temp_port_remove_tree(port, out_path);
+                return copy_result;
+            }
core/tests/fakes/diagnostics/fake_diagnostics_port.c (1)

72-80: Solid dispose path; add fallback free for degenerate state

Lifecycle wiring is correct. As a hardening tweak, free self when vtbl is NULL to avoid leaks if the struct is partially initialized.

 void gm_fake_diag_port_dispose(gm_diagnostics_port_t *port) {
     if (port == NULL) {
         return;
     }
     if (port->vtbl == &VTBL) {
         dispose_impl(port->self);
     } else if (port->vtbl != NULL && port->vtbl->dispose != NULL) {
         port->vtbl->dispose(port->self);
+    } else if (port->vtbl == NULL && port->self != NULL) {
+        /* Degenerate case: free allocated state */
+        free(port->self);
     }
     port->self = NULL;
     port->vtbl = NULL;
 }

Also applies to: 98-106

core/src/journal/writer.c (2)

45-62: Centralize monotonic_ms_now to avoid duplication

This implementation mirrors reader.c. Consider moving to a shared utility (e.g., gitmind/util/time.h) to prevent drift across modules and ease platform fixes.


63-66: Remove unused macro

CLOCKS_PER_MS is no longer used after switching to monotonic timing.

-#define CLOCKS_PER_MS                                                       \
-    ((clock_t)((CLOCKS_PER_SEC + (MILLIS_PER_SECOND - 1)) / MILLIS_PER_SECOND))
core/src/adapters/git/libgit2_repository_port.c (1)

914-931: Use normalized ref name for creation for consistency

Directories are created using norm_ref; pass norm_ref to git_reference_create to keep paths and logs consistent.

-        int create_status = git_reference_create(&created, state->repo,
-                                                 spec->ref_name, spec->target_oid,
+        int create_status = git_reference_create(&created, state->repo,
+                                                 norm_ref, spec->target_oid,
                                                  false, spec->log_message);

Also applies to: 945-954

core/tests/support/temp_repo_helpers.h (2)

17-20: Verify line length compliance and run clang-format.

Line 17 appears to exceed the 80-column limit specified in the coding guidelines. Please run clang-format on this file to ensure consistent formatting.

You can verify line lengths with:

#!/bin/bash
# Check for lines exceeding 80 columns
awk 'length > 80 {print NR": "length" chars: "$0}' core/tests/support/temp_repo_helpers.h

31-37: Consider adding brief documentation comments.

While the function names are descriptive, brief doc comments would help developers understand the contract, especially regarding:

  • Expected component format/constraints
  • Ownership of the created temporary directory
  • When/how to call cleanup

Example format:

+/**
+ * Creates a temporary repository directory using the configured provider.
+ * @param port Filesystem temp port to use
+ * @param component Component name for the temp directory (e.g., "test_foo")
+ * @param out_path Buffer to receive the created path
+ * @param out_size Size of out_path buffer
+ * @return OK on success, error otherwise
+ */
 gm_result_void_t gm_test_make_temp_repo_dir(const gm_fs_temp_port_t *port,
                                              const char *component,
                                              char *out_path,
                                              size_t out_size);

+/**
+ * Removes a temporary repository directory created by gm_test_make_temp_repo_dir.
+ * @param port Filesystem temp port to use
+ * @param path Path to remove (must match a prior make_temp_repo_dir output)
+ * @return OK on success, error otherwise
+ */
 gm_result_void_t gm_test_cleanup_temp_repo_dir(const gm_fs_temp_port_t *port,
                                                 const char *path);

Otherwise, LGTM!

The make/cleanup API pair is well-designed for safe resource management in tests.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 607a20b and 7d95b5a.

📒 Files selected for processing (51)
  • .githooks/pre-push (0 hunks)
  • .github/workflows/apply-feedback.yml (0 hunks)
  • apps/cli/README.md (1 hunks)
  • apps/cli/main.c (3 hunks)
  • apps/cli/main.md (2 hunks)
  • core/include/gitmind/adapters/diagnostics/stderr_diagnostics_adapter.h (1 hunks)
  • core/include/gitmind/adapters/logging/stdio_logger_adapter.h (1 hunks)
  • core/include/gitmind/cache/internal/oid_prefix.h (1 hunks)
  • core/include/gitmind/context.h (1 hunks)
  • core/include/gitmind/journal/internal/append_plan.h (1 hunks)
  • core/include/gitmind/journal/internal/codec.h (1 hunks)
  • core/include/gitmind/journal/internal/read_decoder.h (1 hunks)
  • core/include/gitmind/ports/diagnostic_port.h (2 hunks)
  • core/include/gitmind/ports/fs_temp_port.h (2 hunks)
  • core/include/gitmind/telemetry/internal/config.h (1 hunks)
  • core/include/gitmind/telemetry/internal/log_format.h (1 hunks)
  • core/include/gitmind/telemetry/log_format.h (1 hunks)
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (2 hunks)
  • core/src/adapters/git/libgit2_repository_port.c (2 hunks)
  • core/src/adapters/logging/stdio_logger_adapter.c (2 hunks)
  • core/src/app/cache/cache_rebuild_service.c (2 hunks)
  • core/src/domain/cache/oid_prefix.c (1 hunks)
  • core/src/domain/journal/append_planner.c (1 hunks)
  • core/src/domain/journal/codec.c (2 hunks)
  • core/src/domain/journal/read_decoder.c (2 hunks)
  • core/src/journal/reader.c (5 hunks)
  • core/src/journal/writer.c (4 hunks)
  • core/src/ports/cache/cache_query_port.c (3 hunks)
  • core/src/telemetry/config.c (3 hunks)
  • core/src/telemetry/log_format.c (5 hunks)
  • core/tests/fakes/diagnostics/fake_diagnostics_port.c (2 hunks)
  • core/tests/fakes/diagnostics/fake_diagnostics_port.h (1 hunks)
  • core/tests/integration/test_cache_branch_limits.c (1 hunks)
  • core/tests/integration/test_cache_query.c (1 hunks)
  • core/tests/integration/test_cache_rebuild_canonicalize.c (1 hunks)
  • core/tests/support/temp_repo_helpers.c (1 hunks)
  • core/tests/support/temp_repo_helpers.h (1 hunks)
  • core/tests/unit/support/temp_repo_helpers.h (1 hunks)
  • core/tests/unit/test_cache_oid_prefix.c (1 hunks)
  • core/tests/unit/test_diagnostics_port.c (1 hunks)
  • core/tests/unit/test_journal_nff_retry.c (1 hunks)
  • core/tests/unit/test_log_formatter.c (2 hunks)
  • core/tests/unit/test_telemetry_cfg.c (3 hunks)
  • docs/activity/2025-10-09_pr_feedback.md (1 hunks)
  • docs/architecture/fs_temp_service_plan.md (1 hunks)
  • docs/operations/Debug_Container.md (1 hunks)
  • docs/operations/Diagnostics_Events.md (1 hunks)
  • docs/operations/Telemetry_Config.md (2 hunks)
  • meson.build (7 hunks)
  • tools/ci/ci_local.sh (3 hunks)
  • tools/debug/debug-container.sh (1 hunks)
💤 Files with no reviewable changes (2)
  • .githooks/pre-push
  • .github/workflows/apply-feedback.yml
✅ Files skipped from review due to trivial changes (4)
  • docs/operations/Debug_Container.md
  • core/tests/unit/support/temp_repo_helpers.h
  • core/include/gitmind/ports/fs_temp_port.h
  • core/include/gitmind/telemetry/internal/config.h
🚧 Files skipped from review as they are similar to previous changes (11)
  • core/tests/unit/test_diagnostics_port.c
  • core/tests/unit/test_log_formatter.c
  • core/include/gitmind/cache/internal/oid_prefix.h
  • apps/cli/README.md
  • core/include/gitmind/journal/internal/append_plan.h
  • core/src/ports/cache/cache_query_port.c
  • core/include/gitmind/ports/diagnostic_port.h
  • core/src/adapters/logging/stdio_logger_adapter.c
  • core/src/domain/journal/codec.c
  • docs/operations/Diagnostics_Events.md
  • core/tests/fakes/diagnostics/fake_diagnostics_port.h
🧰 Additional context used
📓 Path-based instructions (14)
{docs/**/*.md,README.md}

📄 CodeRabbit inference engine (AGENTS.md)

Docs must have front matter, a single H1, a '## Table of Contents', and titles matching the H1; API docs include api_version

Files:

  • docs/architecture/fs_temp_service_plan.md
  • docs/operations/Telemetry_Config.md
  • docs/activity/2025-10-09_pr_feedback.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Markdown must satisfy repository markdownlint configuration (.markdownlint.jsonc); use make md-lint/md-fix

Files:

  • docs/architecture/fs_temp_service_plan.md
  • apps/cli/main.md
  • docs/operations/Telemetry_Config.md
  • docs/activity/2025-10-09_pr_feedback.md
**/*.{c,cc,cpp,cxx,h,hpp,hxx,m,mm}

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER use NOLINT to suppress clang-tidy warnings; ALWAYS fix the underlying issue

Files:

  • core/include/gitmind/journal/internal/codec.h
  • core/include/gitmind/adapters/diagnostics/stderr_diagnostics_adapter.h
  • core/tests/unit/test_journal_nff_retry.c
  • core/src/app/cache/cache_rebuild_service.c
  • core/include/gitmind/telemetry/internal/log_format.h
  • core/tests/fakes/diagnostics/fake_diagnostics_port.c
  • core/tests/unit/test_cache_oid_prefix.c
  • core/include/gitmind/telemetry/log_format.h
  • core/tests/integration/test_cache_rebuild_canonicalize.c
  • apps/cli/main.c
  • core/src/domain/journal/read_decoder.c
  • core/src/domain/cache/oid_prefix.c
  • core/tests/integration/test_cache_query.c
  • core/src/journal/reader.c
  • core/tests/support/temp_repo_helpers.c
  • core/tests/integration/test_cache_branch_limits.c
  • core/include/gitmind/adapters/logging/stdio_logger_adapter.h
  • core/src/domain/journal/append_planner.c
  • core/include/gitmind/context.h
  • core/src/telemetry/config.c
  • core/tests/unit/test_telemetry_cfg.c
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/include/gitmind/journal/internal/read_decoder.h
  • core/tests/support/temp_repo_helpers.h
  • core/src/journal/writer.c
  • core/src/telemetry/log_format.c
  • core/src/adapters/git/libgit2_repository_port.c
**/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{c,h}: Use project-safe string/memory helpers: gm_snprintf, gm_strcpy_safe, gm_memcpy_span; check return codes; treat truncation as error
Zero output buffers on error paths before formatting or copying
Prefer GM_PATH_MAX; never use GM_PATH_MAX*2; allocate if larger is required
Language rules: C23, warnings-as-errors; no VLAs; no shadowing; explicit prototypes
Formatting follows .clang-format (LLVM-based, 4 spaces, 80 cols, right-aligned pointers); run clang-format
NEVER use NOLINT or other mechanisms to suppress clang-tidy warnings; fix the warnings instead
Naming: functions/vars lower_snake_case with gm_ prefix; macros UPPER_SNAKE; types end with t; header guards start with GITMIND
Includes: prefer specific headers and order/regroup per .clang-format
Equality is OID-first: when OIDs exist they must match; SHA fallback only when OIDs are absent

Files:

  • core/include/gitmind/journal/internal/codec.h
  • core/include/gitmind/adapters/diagnostics/stderr_diagnostics_adapter.h
  • core/tests/unit/test_journal_nff_retry.c
  • core/src/app/cache/cache_rebuild_service.c
  • core/include/gitmind/telemetry/internal/log_format.h
  • core/tests/fakes/diagnostics/fake_diagnostics_port.c
  • core/tests/unit/test_cache_oid_prefix.c
  • core/include/gitmind/telemetry/log_format.h
  • core/tests/integration/test_cache_rebuild_canonicalize.c
  • apps/cli/main.c
  • core/src/domain/journal/read_decoder.c
  • core/src/domain/cache/oid_prefix.c
  • core/tests/integration/test_cache_query.c
  • core/src/journal/reader.c
  • core/tests/support/temp_repo_helpers.c
  • core/tests/integration/test_cache_branch_limits.c
  • core/include/gitmind/adapters/logging/stdio_logger_adapter.h
  • core/src/domain/journal/append_planner.c
  • core/include/gitmind/context.h
  • core/src/telemetry/config.c
  • core/tests/unit/test_telemetry_cfg.c
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/include/gitmind/journal/internal/read_decoder.h
  • core/tests/support/temp_repo_helpers.h
  • core/src/journal/writer.c
  • core/src/telemetry/log_format.c
  • core/src/adapters/git/libgit2_repository_port.c
{include/**/*.h,core/include/**/*.h}

📄 CodeRabbit inference engine (AGENTS.md)

{include/**/*.h,core/include/**/*.h}: Public headers must be umbrella-safe: compile standalone, correct include order, stable header guards, and extern "C" guards for C++
Public headers must compile standalone (ninja -C build header-compile)

Files:

  • core/include/gitmind/journal/internal/codec.h
  • core/include/gitmind/adapters/diagnostics/stderr_diagnostics_adapter.h
  • core/include/gitmind/telemetry/internal/log_format.h
  • core/include/gitmind/telemetry/log_format.h
  • core/include/gitmind/adapters/logging/stdio_logger_adapter.h
  • core/include/gitmind/context.h
  • core/include/gitmind/journal/internal/read_decoder.h
{include/**/*.h,core/include/gitmind/**/*.h}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid public ABI breaks: append new public struct fields at the tail and mark deprecated fields instead of reordering/removing

Files:

  • core/include/gitmind/journal/internal/codec.h
  • core/include/gitmind/adapters/diagnostics/stderr_diagnostics_adapter.h
  • core/include/gitmind/telemetry/internal/log_format.h
  • core/include/gitmind/telemetry/log_format.h
  • core/include/gitmind/adapters/logging/stdio_logger_adapter.h
  • core/include/gitmind/context.h
  • core/include/gitmind/journal/internal/read_decoder.h
core/include/gitmind/**/*.h

📄 CodeRabbit inference engine (AGENTS.md)

Header guards lint must pass (lint_header_guards); ensure consistent GITMIND_* guards in public headers

Files:

  • core/include/gitmind/journal/internal/codec.h
  • core/include/gitmind/adapters/diagnostics/stderr_diagnostics_adapter.h
  • core/include/gitmind/telemetry/internal/log_format.h
  • core/include/gitmind/telemetry/log_format.h
  • core/include/gitmind/adapters/logging/stdio_logger_adapter.h
  • core/include/gitmind/context.h
  • core/include/gitmind/journal/internal/read_decoder.h
core/tests/unit/test_*.c

📄 CodeRabbit inference engine (AGENTS.md)

Unit tests live in core/tests/unit as test_.c, deterministic and isolated using fakes

Files:

  • core/tests/unit/test_journal_nff_retry.c
  • core/tests/unit/test_cache_oid_prefix.c
  • core/tests/unit/test_telemetry_cfg.c
core/src/**/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

core/src/**/*.{c,h}: Refs must be constructed via gm_build_ref and inputs starting with 'refs/' must be rejected per ref-name safety rules
Journal is append-only under refs/gitmind/edges/; cache under refs/gitmind/cache/ (no merges into cache)
Prefer OID-first APIs using git_oid (gm_oid_t) and compare via git_oid_cmp or gm-level helpers
Wrap all IO behind outbound ports; inject via gm_runtime_context_t; do not call external systems directly (e.g., libgit2) from domain/services
Maintain one responsibility per translation unit; split touched multi-concern files so the edited item lives alone (One-Thing rule)

Files:

  • core/src/app/cache/cache_rebuild_service.c
  • core/src/domain/journal/read_decoder.c
  • core/src/domain/cache/oid_prefix.c
  • core/src/journal/reader.c
  • core/src/domain/journal/append_planner.c
  • core/src/telemetry/config.c
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/journal/writer.c
  • core/src/telemetry/log_format.c
  • core/src/adapters/git/libgit2_repository_port.c
core/tests/fakes/**

📄 CodeRabbit inference engine (AGENTS.md)

Deterministic fakes/mocks/stubs for every port live under core/tests/fakes and are kept in sync with port capabilities

Files:

  • core/tests/fakes/diagnostics/fake_diagnostics_port.c
core/src/domain/**

📄 CodeRabbit inference engine (AGENTS.md)

Pure domain logic resides under core/src/domain/ respecting one-responsibility-per-translation-unit

Files:

  • core/src/domain/journal/read_decoder.c
  • core/src/domain/cache/oid_prefix.c
  • core/src/domain/journal/append_planner.c
meson.build

📄 CodeRabbit inference engine (AGENTS.md)

Project uses Meson with c_std set to c2x (targeting C23) and options centralized in meson_options.txt

Files:

  • meson.build
core/src/telemetry/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

Use the injected log formatter DI seam for structured logs; adapters emit via gm_logger_port

Files:

  • core/src/telemetry/config.c
  • core/src/telemetry/log_format.c
core/src/adapters/**

📄 CodeRabbit inference engine (AGENTS.md)

Runtime adapters live under core/src/adapters//_adapter.c with matching private headers; provide factory/teardown

Files:

  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/adapters/git/libgit2_repository_port.c
🧠 Learnings (8)
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Applies to {core/src/adapters/logging/**/*.c,core/include/gitmind/ports/logger_port.h} : Emit logs via gm_logger_port; format via injected formatter seam

Applied to files:

  • apps/cli/main.c
  • docs/operations/Telemetry_Config.md
  • core/include/gitmind/adapters/logging/stdio_logger_adapter.h
  • core/include/gitmind/context.h
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Applies to core/src/telemetry/*.{c,h} : Use the injected log formatter DI seam for structured logs; adapters emit via gm_logger_port

Applied to files:

  • apps/cli/main.c
  • docs/operations/Telemetry_Config.md
  • core/include/gitmind/context.h
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Applies to {core/src/adapters/libgit2/**/*.c,core/include/gitmind/ports/git_repository_port.h} : Access Git data via gm_git_repository_port (adapter-backed); avoid raw libgit2 usage outside adapters

Applied to files:

  • apps/cli/main.c
  • core/src/adapters/git/libgit2_repository_port.c
📚 Learning: 2025-09-13T03:13:54.145Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-09-13T03:13:54.145Z
Learning: Applies to include/gitmind.h : Maintain umbrella API at include/gitmind.h

Applied to files:

  • core/include/gitmind/context.h
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Applies to {include/**/*.h,core/include/gitmind/**/*.h} : Avoid public ABI breaks: append new public struct fields at the tail and mark deprecated fields instead of reordering/removing

Applied to files:

  • core/include/gitmind/context.h
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Applies to {core/src/adapters/metrics/**/*.c,core/include/gitmind/ports/metrics_port.h} : Emit metrics via gm_metrics_port; default adapter may be null sink

Applied to files:

  • core/include/gitmind/context.h
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Build and tests must run inside the Docker CI image (make ci-local/tools/ci/ci_local.sh); host builds only with explicit override and acceptance of risk

Applied to files:

  • tools/ci/ci_local.sh
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Applies to core/src/**/*.{c,h} : Refs must be constructed via gm_build_ref and inputs starting with 'refs/' must be rejected per ref-name safety rules

Applied to files:

  • core/src/adapters/git/libgit2_repository_port.c
🧬 Code graph analysis (16)
core/include/gitmind/journal/internal/codec.h (1)
core/src/domain/journal/codec.c (2)
  • gm_result_void_t (14-38)
  • gm_result_void_t (40-57)
core/include/gitmind/adapters/diagnostics/stderr_diagnostics_adapter.h (1)
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (2)
  • gm_stderr_diagnostics_port_init (78-91)
  • gm_stderr_diagnostics_port_dispose (93-104)
core/tests/unit/test_journal_nff_retry.c (1)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
core/tests/fakes/diagnostics/fake_diagnostics_port.c (1)
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (2)
  • dispose_impl (69-71)
  • emit_impl (46-67)
core/include/gitmind/telemetry/log_format.h (1)
core/src/telemetry/log_format.c (1)
  • gm_log_format_render_default (94-181)
core/tests/integration/test_cache_rebuild_canonicalize.c (7)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
core/src/types/ulid.c (1)
  • gm_ulid_generate (118-126)
core/src/adapters/fs/posix_temp_adapter.c (1)
  • gm_posix_fs_temp_port_create (628-653)
core/tests/support/temp_repo_helpers.c (2)
  • gm_test_make_temp_repo_dir (239-245)
  • gm_test_cleanup_temp_repo_dir (247-254)
core/src/adapters/git/libgit2_repository_port.c (1)
  • gm_libgit2_repository_port_create (1131-1159)
core/src/journal/writer.c (1)
  • gm_journal_append (466-470)
core/src/cache/builder.c (1)
  • gm_cache_rebuild (9-19)
apps/cli/main.c (4)
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (2)
  • gm_stderr_diagnostics_port_init (78-91)
  • gm_stderr_diagnostics_port_dispose (93-104)
core/src/error/error.c (1)
  • gm_error_free (161-178)
core/src/adapters/logging/stdio_logger_adapter.c (1)
  • gm_stdio_logger_port_dispose (74-79)
core/include/gitmind/ports/diagnostic_port.h (1)
  • gm_diag_reset (50-59)
core/src/domain/journal/read_decoder.c (4)
core/include/gitmind/security/memory.h (1)
  • gm_memset_safe (47-59)
core/include/gitmind/util/memory.h (2)
  • gm_memcpy_span (32-47)
  • gm_strcpy_safe (113-138)
core/src/edge/attributed.c (1)
  • gm_edge_attributed_decode_cbor_ex (290-294)
core/src/edge/edge.c (1)
  • gm_edge_decode_cbor_ex (515-517)
core/tests/integration/test_cache_query.c (1)
core/tests/support/temp_repo_helpers.c (1)
  • gm_test_cleanup_temp_repo_dir (247-254)
core/src/journal/reader.c (2)
core/src/journal/writer.c (1)
  • monotonic_ms_now (45-62)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
core/tests/support/temp_repo_helpers.c (2)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
core/include/gitmind/ports/fs_temp_port.h (6)
  • gm_result_void_t (76-84)
  • gm_result_void_t (86-97)
  • gm_result_void_t (99-108)
  • gm_result_void_t (110-124)
  • gm_result_void_t (126-143)
  • gm_result_void_t (145-152)
core/tests/integration/test_cache_branch_limits.c (1)
core/tests/support/temp_repo_helpers.c (1)
  • gm_test_cleanup_temp_repo_dir (247-254)
core/src/telemetry/config.c (1)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1)
core/tests/fakes/diagnostics/fake_diagnostics_port.c (2)
  • emit_impl (12-70)
  • dispose_impl (72-75)
core/tests/support/temp_repo_helpers.h (1)
core/tests/support/temp_repo_helpers.c (4)
  • gm_test_default_temp_repo_provider (188-232)
  • gm_test_set_temp_repo_dir_provider (234-237)
  • gm_test_make_temp_repo_dir (239-245)
  • gm_test_cleanup_temp_repo_dir (247-254)
core/src/journal/writer.c (2)
core/src/journal/reader.c (1)
  • monotonic_ms_now (476-493)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Compat Canaries (non-blocking) (clang-19)
  • GitHub Check: Compat Canaries (non-blocking) (gcc-13)
  • GitHub Check: Compat Canaries (non-blocking) (gcc-12)
  • GitHub Check: Compat Canaries (non-blocking) (clang-18)
  • GitHub Check: lint-and-sanitize
🔇 Additional comments (39)
core/src/domain/journal/append_planner.c (1)

11-25: LGTM! Parameter rename improves clarity.

The refactoring from empty_tree_oid to tree_oid is accurate and improves code clarity, as the function accepts any tree OID, not just empty trees. All references to the parameter have been correctly updated, and the function behavior remains unchanged.

core/src/app/cache/cache_rebuild_service.c (3)

176-180: LGTM! Better error propagation.

Storing the result in prefix_rc and returning the actual error code improves diagnostics compared to masking failures with a generic error.


347-395: LGTM! Output parameter correctly implemented.

The addition of out_commit_oid allows the caller to capture the commit OID. The implementation follows best practices:

  • Only written on the success path (Line 391-393)
  • Properly null-checked before writing
  • All error paths return early without touching the output parameter
  • Call site at Line 649-651 correctly initializes the variable before passing it

540-540: LGTM! Improved semantic clarity.

Renaming to repo_canon_view better conveys that this is a borrowed reference to canonicalized path data, aligning with the fact that gm_fs_temp_port_canonicalize_ex likely returns a pointer to internal storage. Usage is consistent across the telemetry scaffolding.

Also applies to: 548-549, 554-554

tools/ci/ci_local.sh (1)

55-62: Nice: cross‑platform mktemp fallback added

BSD/macOS compatibility covered with the -t fallback. LGTM.

core/include/gitmind/telemetry/internal/log_format.h (1)

7-7: Good: internal header now forwards to the public API

Keeps a stable include path while consolidating declarations in the public header. LGTM.

core/src/domain/journal/read_decoder.c (1)

68-95: Clarify output requirements in prefer_ modes*

When prefer_attributed=true, out_attr is mandatory (and similarly out_basic when prefer_attributed=false). Ensure header docs reflect this contract so callers don’t expect cross-mode fallback to populate the other output.

Would you like me to update the header docstring in gitmind/journal/internal/read_decoder.h accordingly?

Also applies to: 97-123

core/tests/support/temp_repo_helpers.c (1)

239-254: API alignment: verify header declares provider setter/storage

Ensure temp_repo_helpers.h declares gm_test_set_temp_repo_dir_provider and extern gm_test_temp_repo_provider_storage consistently with intended public surface; otherwise, remove the setter or mark as test-internal.

I can scan headers/usages to confirm and propose a header diff if desired.

core/include/gitmind/adapters/diagnostics/stderr_diagnostics_adapter.h (2)

8-8: LGTM: Include added for completeness.

The result.h include is appropriate since gm_stderr_diagnostics_port_init returns gm_result_void_t. This ensures the header is self-contained and can compile standalone.


16-16: LGTM: Dispose function completes lifecycle API.

The new gm_stderr_diagnostics_port_dispose function provides proper cleanup for the diagnostics port, complementing the existing initializer. The signature is consistent with other disposal functions in the codebase.

core/tests/integration/test_cache_branch_limits.c (1)

145-145: LGTM: Centralized cleanup improves consistency.

The replacement of the direct gm_fs_temp_port_remove_tree call with gm_test_cleanup_temp_repo_dir aligns with the PR's goal of centralizing temporary repository handling. The helper adds input validation and provides a consistent cleanup interface across integration tests.

core/tests/unit/test_cache_oid_prefix.c (5)

30-36: LGTM: Rounding behavior tests added.

The new tests verify that non-multiples of 4 bits are correctly rounded up to the next hex character boundary (5→2 chars, 7→2 chars, 9→3 chars). This validates the implementation's bit-to-hex conversion logic.


38-40: LGTM: Zero-bit edge case covered.

Testing that zero bits yields an empty string ensures the implementation handles the degenerate case correctly without attempting to generate prefix characters.


42-46: LGTM: Error handling paths validated.

The tests correctly verify:

  • NULL oid: returns GM_OK with empty string (graceful degradation per lines 19-21 in oid_prefix.c)
  • NULL dest: returns error code (cannot write output)
  • Zero-sized dest: returns error code (no space for output)

48-50: LGTM: Small buffer edge case tested.

Verifying that a 2-byte buffer is properly NUL-terminated and limited to 1 hex character ensures the implementation respects buffer boundaries and prevents overflows.


52-54: LGTM: Boundary clamping verified.

Testing that excessive bit counts (1024) are clamped to GM_CACHE_MAX_SHARD_PATH - 1 ensures the implementation enforces conservative maximum prefix lengths for directory names.

apps/cli/main.md (2)

94-94: LGTM: Safety override options clearly documented.

The enumeration of falsy (off, false, 0, no) and truthy (on, true, 1, default unset) values for GITMIND_SAFETY provides clear guidance for users who need to disable guardrails in CI/E2E environments.


259-259: LGTM: Practical JSON logging example added.

The example demonstrates a complete workflow: setting the format, redirecting logs, and processing with jq. This helps users understand how to leverage structured JSON logs in practice.

core/tests/unit/test_telemetry_cfg.c (3)

8-12: LGTM: Windows portability added.

The conditional compilation correctly handles the difference between Windows (_putenv) and POSIX (putenv) environment variable APIs. The #ifdef _WIN32 guard is the standard approach for Windows detection.


25-30: LGTM: Conditional putenv call is correct.

The runtime call to _putenv on Windows and putenv on other platforms matches the declarations and ensures cross-platform compatibility. The heap-allocated string is intentionally leaked per putenv semantics (the string must persist).


54-55: LGTM: Substring checks provide flexibility.

Changing from exact string comparison to substring checks (strstr) allows the tests to pass when additional tags are present, making the tests more robust to implementation changes in tag ordering or extra tag injection.

core/include/gitmind/journal/internal/codec.h (1)

24-32: LGTM: Unsafe decode API fixed with capacity parameter.

The addition of the decoded_capacity parameter addresses the critical memory-safety issue flagged in the past review. The API now:

  • Explicitly requires the caller to provide buffer capacity
  • Documents ownership clearly ("caller owns the decoded buffer")
  • Returns the actual decoded length via decoded_length

The implementation (codec.c lines 39-56) validates decoded_capacity != 0 and passes it to sodium_base642bin, preventing buffer overflows.

Note: This resolves the past review comment about the unsafe decode API.

core/src/domain/cache/oid_prefix.c (4)

22-24: LGTM: Type safety improved with size_t.

Using size_t for the character count calculation and clamping eliminates potential signed/unsigned comparison issues and aligns with the buffer size type (out_size is size_t). The explicit cast to size_t on Line 22 and clamping on Line 24 ensure safe arithmetic.


25-27: LGTM: Simplified boundary check.

The direct size_t comparison against out_size is clearer and safer than the previous cast-based approach. Clamping to out_size - 1 correctly reserves space for the NUL terminator.


34-39: LGTM: Conservative maximum enforced with early exit.

The explicit cast of GM_CACHE_MAX_SHARD_PATH - 1 to size_t ensures type consistency, and the early return when chars == 0 avoids unnecessary loop iteration. This enforces the documented directory name length limit.


41-43: LGTM: Loop iterator type aligned with size_t.

Changing the loop variable from int to size_t matches the type of chars and eliminates signed/unsigned comparison warnings, improving type safety and consistency.

docs/operations/Telemetry_Config.md (3)

57-57: LGTM: Extras cap documented clearly.

The documentation now explicitly states the cap of 3 slots for extra metrics tags and notes that it auto-derives from gm_telemetry_cfg_t.extras. This sets clear expectations for users and ties the limit to the implementation.


61-62: LGTM: Deterministic JSON ordering documented.

Documenting that the JSON formatter emits keys in lexical order helps users understand that the output is stable and predictable, which is important for downstream tooling and testing.

Based on learnings: Structured logs should use the injected log formatter DI seam.


87-92: LGTM: Formatter seam extension point documented.

The new section clearly explains:

  • Where the formatter hook lives (gitmind/telemetry/log_format.h)
  • How to override it (ctx.log_formatter = my_formatter)
  • What formatters receive (structured key/value list, JSON toggle)
  • Expected return behavior

This provides a clear extension point for custom formatting needs.

Based on learnings: The formatter DI seam is now properly documented for adapters.

core/tests/integration/test_cache_query.c (1)

147-151: Switch to gm_test_cleanup_temp_repo_dir looks good

Centralizes temp-repo cleanup with input validation; error is freed on failure. Cleanup order remains correct.

apps/cli/main.c (3)

252-269: Diagnostics and logger wiring is robust

  • Guarded init with error-freeing.
  • Stores dispose hooks on success.
  • Correct log-level enum usage.

284-295: Cleanup correctly disposes logger and diagnostics with safe fallbacks

Balanced teardown using stored disposers; gm_diag_reset used when not set. Looks good.


14-16: Redundant include: gitmind/context.h (included at the top) already pulls in telemetry/log_format.h, so no extra include is required.

Likely an incorrect or invalid review comment.

core/src/telemetry/log_format.c (1)

72-121: Deterministic JSON ordering and cleanup paths look correct

Insertion sort over a small kv set is fine; ordered array is freed on all paths. Bounds checks leave space for the final NUL.

core/src/journal/writer.c (1)

342-363: Repo canonicalization lifetime and monotonic timing: LGTM

  • Copying canon_view into a local buffer before deriving repo_id avoids dangling pointers.
  • Monotonic timing for duration is correct and cross‑platform.

Also applies to: 398-406

core/src/adapters/git/libgit2_repository_port.c (1)

800-814: Commit signature fallback behavior is correct

Handles ENOTFOUND by synthesizing a signature; surfaces other errors. Good guardrails.

core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1)

69-76: Escape handling and disposal wiring look good

  • emit_escaped safely handles control chars.
  • VTBL.dispose and public dispose free state and reset pointers.

Also applies to: 93-104

core/tests/support/temp_repo_helpers.h (2)

22-22: LGTM!

Exposing the provider storage as an extern symbol is appropriate for test flexibility, and the implementation properly handles initialization.


24-29: LGTM!

The function signatures are well-designed and follow project conventions. The provider pattern with set/default functions offers good flexibility for test scenarios.

@flyingrobots
Copy link
Member Author

Addressed feedback: diagnostics emit guard, journal converter safety with error propagation, telemetry context disposal, mktemp portability, doc front matter/TOC, and docker-only guard in ci-local. Latest run: make ci-local.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
core/src/telemetry/config.c (1)

273-278: Free the error returned from gm_crypto_context_create.

If gm_crypto_context_create fails, cr.u.err is never released, leaking a gm_error_t. Please free it before falling back to FNV.

-    if (!cr.ok) {
+    if (!cr.ok) {
+        if (cr.u.err != NULL) {
+            gm_error_free(cr.u.err);
+        }
         fnv1a64_hex12(data, len, out12);
         return;
     }
♻️ Duplicate comments (1)
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1)

49-51: Inconsistent NULL handling compared to fake diagnostics port.

The stderr adapter silently returns gm_ok_void() when kvs == NULL and kv_count > 0, while the fake diagnostics port (core/tests/fakes/diagnostics/fake_diagnostics_port.c:17-19) returns an error. Silent success can hide bugs in caller code.

Apply this diff to match the fake port's behavior:

     if (kv_count > 0U && kvs == NULL) {
-        return gm_ok_void();
+        return gm_err_void(GM_ERROR(GM_ERR_INVALID_ARGUMENT,
+                                    "diagnostics kv list missing"));
     }
🧹 Nitpick comments (2)
core/src/crypto/backend.c (1)

105-112: LGTM! Consider lenient NULL handling.

The disposal logic is correct and safe. The function properly validates input and clears the backend reference.

Optional: Many disposal APIs treat NULL as a no-op rather than an error, which simplifies cleanup code (callers don't need to check before disposing). Consider whether this stricter approach aligns with the project's disposal conventions, especially given similar disposal functions mentioned in the AI summary (e.g., gm_diag_reset, gm_stderr_diagnostics_port_dispose).

If lenient NULL handling is preferred:

 gm_result_void_t gm_crypto_context_dispose(gm_crypto_context_t *ctx) {
-    if (ctx == NULL) {
-        return gm_err_void(GM_ERROR(GM_ERR_INVALID_ARGUMENT,
-                                    "crypto context cannot be null"));
+    if (ctx != NULL) {
+        ctx->backend = NULL;
     }
-    ctx->backend = NULL;
     return gm_ok_void();
 }
core/include/gitmind/crypto/backend.h (1)

70-71: Add documentation for the new public API.

The function declaration is correct, but it lacks documentation. Public API functions should be documented to describe their purpose, parameters, return values, and behavior (e.g., idempotency, NULL handling).

Add a documentation comment above the declaration:

+/**
+ * @brief Dispose of a crypto context by clearing its backend reference
+ *
+ * Clears the backend pointer in the provided context. The context structure
+ * itself is not freed; the caller retains ownership.
+ *
+ * @param ctx Pointer to the crypto context to dispose (must not be NULL)
+ * @return GM_OK on success, or GM_ERR_INVALID_ARGUMENT if ctx is NULL
+ */
 gm_result_void_t gm_crypto_context_dispose(gm_crypto_context_t *ctx);
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d95b5a and baa4af9.

📒 Files selected for processing (8)
  • core/include/gitmind/crypto/backend.h (1 hunks)
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (2 hunks)
  • core/src/crypto/backend.c (1 hunks)
  • core/src/domain/journal/read_decoder.c (2 hunks)
  • core/src/telemetry/config.c (4 hunks)
  • docs/activity/2025-10-09_pr_feedback.md (1 hunks)
  • tools/ci/ci_local.sh (4 hunks)
  • tools/debug/debug-container.sh (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tools/debug/debug-container.sh
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{c,cc,cpp,cxx,h,hpp,hxx,m,mm}

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER use NOLINT to suppress clang-tidy warnings; ALWAYS fix the underlying issue

Files:

  • core/include/gitmind/crypto/backend.h
  • core/src/domain/journal/read_decoder.c
  • core/src/crypto/backend.c
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/telemetry/config.c
**/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{c,h}: Use project-safe string/memory helpers: gm_snprintf, gm_strcpy_safe, gm_memcpy_span; check return codes; treat truncation as error
Zero output buffers on error paths before formatting or copying
Prefer GM_PATH_MAX; never use GM_PATH_MAX*2; allocate if larger is required
Language rules: C23, warnings-as-errors; no VLAs; no shadowing; explicit prototypes
Formatting follows .clang-format (LLVM-based, 4 spaces, 80 cols, right-aligned pointers); run clang-format
NEVER use NOLINT or other mechanisms to suppress clang-tidy warnings; fix the warnings instead
Naming: functions/vars lower_snake_case with gm_ prefix; macros UPPER_SNAKE; types end with t; header guards start with GITMIND
Includes: prefer specific headers and order/regroup per .clang-format
Equality is OID-first: when OIDs exist they must match; SHA fallback only when OIDs are absent

Files:

  • core/include/gitmind/crypto/backend.h
  • core/src/domain/journal/read_decoder.c
  • core/src/crypto/backend.c
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/telemetry/config.c
{include/**/*.h,core/include/**/*.h}

📄 CodeRabbit inference engine (AGENTS.md)

{include/**/*.h,core/include/**/*.h}: Public headers must be umbrella-safe: compile standalone, correct include order, stable header guards, and extern "C" guards for C++
Public headers must compile standalone (ninja -C build header-compile)

Files:

  • core/include/gitmind/crypto/backend.h
{include/**/*.h,core/include/gitmind/**/*.h}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid public ABI breaks: append new public struct fields at the tail and mark deprecated fields instead of reordering/removing

Files:

  • core/include/gitmind/crypto/backend.h
core/include/gitmind/**/*.h

📄 CodeRabbit inference engine (AGENTS.md)

Header guards lint must pass (lint_header_guards); ensure consistent GITMIND_* guards in public headers

Files:

  • core/include/gitmind/crypto/backend.h
core/src/domain/**

📄 CodeRabbit inference engine (AGENTS.md)

Pure domain logic resides under core/src/domain/ respecting one-responsibility-per-translation-unit

Files:

  • core/src/domain/journal/read_decoder.c
core/src/**/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

core/src/**/*.{c,h}: Refs must be constructed via gm_build_ref and inputs starting with 'refs/' must be rejected per ref-name safety rules
Journal is append-only under refs/gitmind/edges/; cache under refs/gitmind/cache/ (no merges into cache)
Prefer OID-first APIs using git_oid (gm_oid_t) and compare via git_oid_cmp or gm-level helpers
Wrap all IO behind outbound ports; inject via gm_runtime_context_t; do not call external systems directly (e.g., libgit2) from domain/services
Maintain one responsibility per translation unit; split touched multi-concern files so the edited item lives alone (One-Thing rule)

Files:

  • core/src/domain/journal/read_decoder.c
  • core/src/crypto/backend.c
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/telemetry/config.c
{docs/**/*.md,README.md}

📄 CodeRabbit inference engine (AGENTS.md)

Docs must have front matter, a single H1, a '## Table of Contents', and titles matching the H1; API docs include api_version

Files:

  • docs/activity/2025-10-09_pr_feedback.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Markdown must satisfy repository markdownlint configuration (.markdownlint.jsonc); use make md-lint/md-fix

Files:

  • docs/activity/2025-10-09_pr_feedback.md
core/src/adapters/**

📄 CodeRabbit inference engine (AGENTS.md)

Runtime adapters live under core/src/adapters//_adapter.c with matching private headers; provide factory/teardown

Files:

  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
core/src/telemetry/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

Use the injected log formatter DI seam for structured logs; adapters emit via gm_logger_port

Files:

  • core/src/telemetry/config.c
🧠 Learnings (1)
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Build and tests must run inside the Docker CI image (make ci-local/tools/ci/ci_local.sh); host builds only with explicit override and acceptance of risk

Applied to files:

  • tools/ci/ci_local.sh
🧬 Code graph analysis (4)
core/include/gitmind/crypto/backend.h (1)
core/src/crypto/backend.c (1)
  • gm_crypto_context_dispose (105-112)
core/src/domain/journal/read_decoder.c (4)
core/include/gitmind/security/memory.h (1)
  • gm_memset_safe (47-59)
core/include/gitmind/util/memory.h (2)
  • gm_memcpy_span (32-47)
  • gm_strcpy_safe (113-138)
core/src/edge/attributed.c (1)
  • gm_edge_attributed_decode_cbor_ex (290-294)
core/src/edge/edge.c (1)
  • gm_edge_decode_cbor_ex (515-517)
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1)
core/tests/fakes/diagnostics/fake_diagnostics_port.c (2)
  • emit_impl (12-70)
  • dispose_impl (72-75)
core/src/telemetry/config.c (3)
core/src/crypto/backend.c (1)
  • gm_crypto_context_dispose (105-112)
core/src/error/error.c (1)
  • gm_error_free (161-178)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: coderabbit
  • GitHub Check: Analyze (c-cpp)
🔇 Additional comments (12)
core/src/domain/journal/read_decoder.c (3)

18-53: Past review feedback successfully addressed.

The converter now returns gm_result_void_t, validates inputs, and checks every gm_memcpy_span and gm_strcpy_safe return code. Truncation and buffer overflows are treated as errors with field-specific error messages. This implementation follows the recommended option to check returns and propagate failures.

Based on past review comments


55-88: Converter correctly implements safe-copy error checking.

This converter mirrors the approach in convert_legacy_to_attributed: validates inputs, checks all copy operations, and returns field-specific errors on overflow or truncation.

Based on past review comments


90-165: Decoder properly propagates converter errors.

The decode function correctly handles the new converter signatures by:

  • Validating output pointers are non-NULL before use (lines 112-115, 127-130, 142-145, 156-159)
  • Checking converter results and returning early on failure (lines 131-132, 160-161)
  • Properly setting metadata (consumed, got_attr) in all success paths

This ensures conversion errors are propagated to the caller rather than silently ignored.

Based on past review comments

tools/ci/ci_local.sh (4)

12-21: LGTM! Clean staging cleanup with optional retention.

The cleanup function and trap properly handle temporary staging directory removal with an opt-in retention flag for debugging.


58-68: Portable mktemp implementation addresses previous feedback.

The two-stage fallback (GNU syntax first, then BSD -t flag) ensures cross-platform compatibility. This resolves the portability concern raised in the previous review.


94-122: LGTM! Proper status capture from container execution.

The script correctly disables set -e before the docker run, captures the container's exit status, then re-enables set -e before artifact collection. This ensures CI failures are properly propagated.


124-143: LGTM! Comprehensive artifact collection with diff reporting.

The artifact collection properly copies reports back from staging and generates a useful diff when previous clang-tidy reports exist.

core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (4)

18-44: LGTM! Robust escaping for diagnostic output.

The emit_escaped helper properly handles NULL values and escapes control characters, preventing garbled or misleading diagnostic output.


52-55: LGTM! Proper emit guard respects disabled state.

The emit guard correctly checks for NULL state and the emit_enabled flag, allowing diagnostics to be disabled at runtime.


96-107: Clarify the fallback disposal logic.

Lines 100-103 check if the vtbl matches the stderr adapter's VTBL, and if not, fall back to calling port->vtbl->dispose. This suggests gm_stderr_diagnostics_port_dispose might be called with a port that wasn't initialized by this adapter.

Is this fallback intentional (e.g., polymorphic cleanup) or defensive programming? If callers should only pass stderr-initialized ports, the fallback is misleading and should be removed or documented.


14-16: State field refactor addresses previous feedback.

The field is now bool emit_enabled (previously int enabled) and is actively used in emit_impl to control diagnostic output. This resolves the "unused field" concern from the previous review.

docs/activity/2025-10-09_pr_feedback.md (1)

1-30: LGTM! Document structure complies with guidelines.

The document now includes required front matter, a matching H1 title, and a Table of Contents, addressing all concerns from the previous review. The content provides a clear summary of PR feedback remediation.

As per coding guidelines

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
core/src/telemetry/config.c (1)

278-299: Free the crypto error before falling back.

If gm_crypto_context_create() fails, cr.u.err is left allocated—nothing frees it before returning via the FNV fallback. Please release the error (and similarly guard any other early returns) so we do not leak gm_error_t instances on repeated failures.

-    gm_result_crypto_context_t cr = gm_crypto_context_create(gm_crypto_backend_libsodium());
-    if (!cr.ok) {
-        /* Fallback to FNV on any error */
-        fnv1a64_hex12(data, len, out12);
-        return;
-    }
+    gm_result_crypto_context_t cr =
+        gm_crypto_context_create(gm_crypto_backend_libsodium());
+    if (!cr.ok) {
+        if (cr.u.err != NULL) {
+            gm_error_free(cr.u.err);
+        }
+        /* Fallback to FNV on any error */
+        fnv1a64_hex12(data, len, out12);
+        return;
+    }
core/src/adapters/fs/posix_temp_adapter.c (1)

279-308: Handle gm_snprintf truncation paths.

gm_snprintf returns the would-be length on truncation, so the < 0 guard misses overflow cases. We end up accepting truncated paths (violating the “treat truncation as error” rule) and operating on the wrong directories. Please capture the return value and reject any result whose length is >= sizeof(buffer) here and in the other gm_snprintf call sites in this file (e.g., Lines 308, 446, 465).

-            if (gm_snprintf(state->base_state, sizeof(state->base_state),
-                            "%s/.gitmind", home) < 0) {
+            int wrote = gm_snprintf(state->base_state, sizeof(state->base_state),
+                                    "%s/.gitmind", home);
+            if (wrote < 0 || (size_t)wrote >= sizeof(state->base_state)) {
                 return gm_err_void(GM_ERROR(GM_ERR_PATH_TOO_LONG,
                                             "failed to compose state base"));
             }

Please apply the same pattern to the other gm_snprintf sites touched in this file. As per coding guidelines.

🧹 Nitpick comments (1)
core/src/journal/reader.c (1)

149-156: Consider simplifying error handling for clarity.

The error handling is correct but checks dr.ok twice (lines 152 and 155). You could simplify:

-    gm_result_void_t dr =
-        gm_journal_decode_message(raw_message, decoded, sizeof(decoded), &message_len);
-    int decode_status = dr.ok ? GM_OK : (dr.u.err ? dr.u.err->code : GM_ERR_UNKNOWN);
-    if (!dr.ok) {
-        message_len = 0;
-    }
-    if (!dr.ok && dr.u.err) gm_error_free(dr.u.err);
-    if (decode_status != GM_OK) {
-        return decode_status;
+    gm_result_void_t dr =
+        gm_journal_decode_message(raw_message, decoded, sizeof(decoded), &message_len);
+    if (!dr.ok) {
+        message_len = 0;
+        int decode_status = dr.u.err ? dr.u.err->code : GM_ERR_UNKNOWN;
+        if (dr.u.err) gm_error_free(dr.u.err);
+        return decode_status;
     }
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ac6073 and 95d398a.

📒 Files selected for processing (11)
  • AGENTS.md (2 hunks)
  • core/include/gitmind/telemetry/internal/config.h (4 hunks)
  • core/src/adapters/config/env_adapter.c (2 hunks)
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1 hunks)
  • core/src/adapters/fs/posix_temp_adapter.c (18 hunks)
  • core/src/app/cache/cache_rebuild_service.c (2 hunks)
  • core/src/journal/reader.c (6 hunks)
  • core/src/journal/writer.c (4 hunks)
  • core/src/telemetry/config.c (7 hunks)
  • core/src/telemetry/log_format.c (3 hunks)
  • core/tests/unit/test_telemetry_cfg.c (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • AGENTS.md
  • core/include/gitmind/telemetry/internal/config.h
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{c,cc,cpp,cxx,h,hpp,hxx,m,mm}

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER use NOLINT to suppress clang-tidy warnings; ALWAYS fix the underlying issue

Files:

  • core/src/journal/writer.c
  • core/src/journal/reader.c
  • core/tests/unit/test_telemetry_cfg.c
  • core/src/app/cache/cache_rebuild_service.c
  • core/src/telemetry/log_format.c
  • core/src/adapters/config/env_adapter.c
  • core/src/telemetry/config.c
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/adapters/fs/posix_temp_adapter.c
**/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{c,h}: Use project-safe string/memory helpers: gm_snprintf, gm_strcpy_safe, gm_memcpy_span; check return codes; treat truncation as error
Zero output buffers on error paths before formatting or copying
Prefer GM_PATH_MAX; never use GM_PATH_MAX*2; allocate if larger is required
Language rules: C23, warnings-as-errors; no VLAs; no shadowing; explicit prototypes
Formatting follows .clang-format (LLVM-based, 4 spaces, 80 cols, right-aligned pointers); run clang-format
NEVER use NOLINT or other mechanisms to suppress clang-tidy warnings; fix the warnings instead
Naming: functions/vars lower_snake_case with gm_ prefix; macros UPPER_SNAKE; types end with t; header guards start with GITMIND
Includes: prefer specific headers and order/regroup per .clang-format
Equality is OID-first: when OIDs exist they must match; SHA fallback only when OIDs are absent

Files:

  • core/src/journal/writer.c
  • core/src/journal/reader.c
  • core/tests/unit/test_telemetry_cfg.c
  • core/src/app/cache/cache_rebuild_service.c
  • core/src/telemetry/log_format.c
  • core/src/adapters/config/env_adapter.c
  • core/src/telemetry/config.c
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/adapters/fs/posix_temp_adapter.c
core/src/**/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

core/src/**/*.{c,h}: Refs must be constructed via gm_build_ref and inputs starting with 'refs/' must be rejected per ref-name safety rules
Journal is append-only under refs/gitmind/edges/; cache under refs/gitmind/cache/ (no merges into cache)
Prefer OID-first APIs using git_oid (gm_oid_t) and compare via git_oid_cmp or gm-level helpers
Wrap all IO behind outbound ports; inject via gm_runtime_context_t; do not call external systems directly (e.g., libgit2) from domain/services
Maintain one responsibility per translation unit; split touched multi-concern files so the edited item lives alone (One-Thing rule)

Files:

  • core/src/journal/writer.c
  • core/src/journal/reader.c
  • core/src/app/cache/cache_rebuild_service.c
  • core/src/telemetry/log_format.c
  • core/src/adapters/config/env_adapter.c
  • core/src/telemetry/config.c
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/adapters/fs/posix_temp_adapter.c
core/tests/unit/test_*.c

📄 CodeRabbit inference engine (AGENTS.md)

Unit tests live in core/tests/unit as test_.c, deterministic and isolated using fakes

Files:

  • core/tests/unit/test_telemetry_cfg.c
core/src/telemetry/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

Use the injected log formatter DI seam for structured logs; adapters emit via gm_logger_port

Files:

  • core/src/telemetry/log_format.c
  • core/src/telemetry/config.c
core/src/adapters/**

📄 CodeRabbit inference engine (AGENTS.md)

Runtime adapters live under core/src/adapters//_adapter.c with matching private headers; provide factory/teardown

Files:

  • core/src/adapters/config/env_adapter.c
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/adapters/fs/posix_temp_adapter.c
{core/src/adapters/config/**/*.c,core/include/gitmind/ports/env_port.h}

📄 CodeRabbit inference engine (AGENTS.md)

Read environment/config via gm_env_port; avoid direct getenv

Files:

  • core/src/adapters/config/env_adapter.c
{core/src/adapters/fs/**/*.c,core/include/gitmind/ports/filesystem_port.h}

📄 CodeRabbit inference engine (AGENTS.md)

Use gm_fs_port for filesystem operations; no direct POSIX calls in domain/services

Files:

  • core/src/adapters/fs/posix_temp_adapter.c
🧠 Learnings (2)
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Applies to {core/src/adapters/config/**/*.c,core/include/gitmind/ports/env_port.h} : Read environment/config via gm_env_port; avoid direct getenv

Applied to files:

  • core/src/adapters/config/env_adapter.c
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Applies to {core/src/adapters/fs/**/*.c,core/include/gitmind/ports/filesystem_port.h} : Use gm_fs_port for filesystem operations; no direct POSIX calls in domain/services

Applied to files:

  • core/src/adapters/fs/posix_temp_adapter.c
🧬 Code graph analysis (5)
core/src/journal/writer.c (2)
core/src/journal/reader.c (1)
  • monotonic_ms_now (481-498)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
core/src/journal/reader.c (2)
core/src/journal/writer.c (1)
  • monotonic_ms_now (45-62)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
core/src/telemetry/config.c (4)
core/include/gitmind/security/memory.h (1)
  • gm_memset_safe (47-59)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
core/src/crypto/backend.c (1)
  • gm_crypto_context_dispose (105-112)
core/src/error/error.c (1)
  • gm_error_free (161-178)
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1)
core/tests/fakes/diagnostics/fake_diagnostics_port.c (2)
  • emit_impl (12-70)
  • dispose_impl (72-75)
core/src/adapters/fs/posix_temp_adapter.c (2)
core/include/gitmind/util/memory.h (1)
  • gm_memcpy_span (32-47)
core/src/error/error.c (1)
  • gm_error_free (161-178)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Compat Canaries (non-blocking) (gcc-12)
  • GitHub Check: Compat Canaries (non-blocking) (clang-18)
  • GitHub Check: Compat Canaries (non-blocking) (gcc-13)
  • GitHub Check: Compat Canaries (non-blocking) (clang-19)
  • GitHub Check: lint-and-sanitize
  • GitHub Check: build-test
🔇 Additional comments (10)
core/src/adapters/config/env_adapter.c (2)

8-38: LGTM: Proper error handling and safe string operations.

The enhanced error handling with GM_ERROR macros and the use of gm_strcpy_safe for buffer management align with the project's coding guidelines. The NULL check for getenv return value with proper empty-string fallback is defensive and correct.


40-49: LGTM: Naming improvements for clarity.

Renaming the vtbl to GmEnvPortSystemVtbl and the port instance to system_port makes the code more explicit and consistent with other adapter implementations in the PR.

core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (5)

17-17: Past issue resolved: struct field now actively used.

The struct field has been changed from int enabled to bool emit_enabled and is now actively used in the emit logic (line 62). This addresses the previous feedback about the unused field.


20-53: LGTM: Robust escaping with proper safety checks.

The emit_escaped helper correctly handles NULL input, escapes common control characters, and uses gm_snprintf for hex escaping with appropriate buffer sizing (5 bytes for \xNN\0). The cast to unsigned char on line 26 is correct for iscntrl. The error check on line 44 silently drops malformed escapes, which is acceptable for diagnostic output.


55-83: Past issue resolved: NULL kvs guard added.

Lines 58-60 now guard against dereferencing NULL kvs when kv_count > 0, addressing the critical issue flagged in the previous review. The implementation returns early with gm_ok_void() rather than an error, which is a reasonable choice for diagnostic output.


85-109: Past issue resolved: dispose implementation added.

The dispose_impl function (lines 85-87) now properly frees the allocated state, and is wired into the vtbl (line 91). The init function allocates state via calloc (lines 99-104) and initializes emit_enabled to true (line 105). This addresses the memory leak concern from the previous review.


111-122: Defensive dispose implementation handles multiple vtbl types.

The public dispose function uses identity comparison (port->vtbl == &GmStderrDiagnosticsPortVtbl) to detect the stderr vtbl, then falls back to a generic vtbl dispose call if available. This defensive pattern safely handles both the specific adapter and any other vtbl implementations, then clears port fields to prevent use-after-free.

core/src/journal/reader.c (3)

28-42: LGTM! Header inclusions and forward declaration are correct.

The Windows-specific header is needed for performance counter APIs, the log_format.h enables the telemetry formatter seam, and the forward declaration properly handles the function being used before its definition.


336-342: LGTM! Canonicalization view pattern improves safety.

The change from copying to using a view reference is good design. The error handling correctly treats truncation as an error per coding guidelines: if gm_strcpy_safe returns -1, repo_canon remains NULL and a warning is logged (lines 352-354).


366-413: LGTM! Telemetry and timing improvements are well-designed.

The structured gm_telemetry_tag_context_t (lines 366-373) provides better organization, and using monotonic time (lines 410, 413) is more reliable than wall-clock time for duration measurements. The edge counting via edge_count_ptr correctly increments for both attributed and non-attributed edges (lines 112, 116).

GM_ERROR(GM_ERR_IO_FAILED, "unlinkat failed for %s: %s", name,
strerror(errno)));
if (!S_ISDIR(root_stat.st_mode)) {
if (unlink(normalized) != 0 && errno != ENOENT) {

Check failure

Code scanning / CodeQL

Time-of-check time-of-use filesystem race condition High

The
filename
being operated upon was previously
checked
, but the underlying file may have been changed since then.
The
filename
being operated upon was previously
checked
, but the underlying file may have been changed since then.

Copilot Autofix

AI about 2 months ago

General approach

The classic way to prevent TOCTOU vulnerabilities is to avoid acting on file names after a check—instead, act on open file descriptors. When deleting a non-directory file (not a directory itself), instead of calling stat() then unlink(path), you can open the file with open() using the O_NOFOLLOW and (if supported) O_PATH flags. Then, unlink through an open file descriptor using unlinkat(), making sure not to follow symlinks.

Detailed fix

In this code block:

  • Instead of calling stat on the path, then unlink, open the file with open() using O_NOFOLLOW (and O_PATH if available), and then call unlinkat() using the open directory file descriptor and filename (preferably).
  • However, unlinkat() accepts a directory file descriptor—by opening the parent directory, you can modify the specific entry in a race-resistant way.
  • If possible, parse the path, open the parent directory with O_DIRECTORY, then call unlinkat() on the filename relative to that fd, with the AT_REMOVEDIR flag unset.
  • Only if this fails with ENOENT does the function return OK, otherwise it issues an error.

Concrete code changes

  • Introduce #include <fcntl.h> if not present.
  • In the code block for removing a non-directory file (i.e., the !S_ISDIR(root_stat.st_mode) branch), instead of unlink(normalized), parse normalized into parent directory and file name, open parent directory with open()/O_DIRECTORY, and call unlinkat() on the file.

These changes all occur within core/src/adapters/fs/posix_temp_adapter.c, specifically in the non-directory-removal branch within remove_tree_impl.


Suggested changeset 1
core/src/adapters/fs/posix_temp_adapter.c

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/core/src/adapters/fs/posix_temp_adapter.c b/core/src/adapters/fs/posix_temp_adapter.c
--- a/core/src/adapters/fs/posix_temp_adapter.c
+++ b/core/src/adapters/fs/posix_temp_adapter.c
@@ -12,10 +12,11 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <libgen.h>
 #include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
-
+#include <fcntl.h>
 #include "gitmind/constants_internal.h"
 #include "gitmind/error.h"
 #include "gitmind/fs/path_utils.h"
@@ -518,9 +516,32 @@
     }
 
     if (!S_ISDIR(root_stat.st_mode)) {
-        if (unlink(normalized) != 0 && errno != ENOENT) {
-            return errno_to_result("unlink", normalized);
+        // Use unlinkat to reduce TOCTOU window
+        char parent_dir[GM_PATH_MAX];
+        char file_name[GM_PATH_MAX];
+        // Copy normalized to get dirname and basename separately
+        if (gm_strcpy_safe(parent_dir, sizeof(parent_dir), normalized) != GM_OK ||
+            gm_strcpy_safe(file_name, sizeof(file_name), normalized) != GM_OK) {
+            return gm_err_void(
+                GM_ERROR(GM_ERR_PATH_TOO_LONG,
+                        "normalized path exceeds buffer while parsing path"));
         }
+        // Use POSIX dirname and basename (thread unsafe), so save before use
+        char *dir = dirname(parent_dir);
+        char *base = basename(file_name);
+        int dfd = open(dir, O_DIRECTORY | O_CLOEXEC);
+        if (dfd == -1) {
+            if (errno == ENOENT) {
+                return gm_ok_void();
+            }
+            return errno_to_result("open (parent dir)", dir);
+        }
+        int rc = unlinkat(dfd, base, 0);
+        int saved_errno = errno;
+        close(dfd);
+        if (rc != 0 && saved_errno != ENOENT) {
+            return errno_to_result("unlinkat", normalized);
+        }
         return gm_ok_void();
     }
 
EOF
@@ -12,10 +12,11 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <libgen.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include <fcntl.h>
#include "gitmind/constants_internal.h"
#include "gitmind/error.h"
#include "gitmind/fs/path_utils.h"
@@ -518,9 +516,32 @@
}

if (!S_ISDIR(root_stat.st_mode)) {
if (unlink(normalized) != 0 && errno != ENOENT) {
return errno_to_result("unlink", normalized);
// Use unlinkat to reduce TOCTOU window
char parent_dir[GM_PATH_MAX];
char file_name[GM_PATH_MAX];
// Copy normalized to get dirname and basename separately
if (gm_strcpy_safe(parent_dir, sizeof(parent_dir), normalized) != GM_OK ||
gm_strcpy_safe(file_name, sizeof(file_name), normalized) != GM_OK) {
return gm_err_void(
GM_ERROR(GM_ERR_PATH_TOO_LONG,
"normalized path exceeds buffer while parsing path"));
}
// Use POSIX dirname and basename (thread unsafe), so save before use
char *dir = dirname(parent_dir);
char *base = basename(file_name);
int dfd = open(dir, O_DIRECTORY | O_CLOEXEC);
if (dfd == -1) {
if (errno == ENOENT) {
return gm_ok_void();
}
return errno_to_result("open (parent dir)", dir);
}
int rc = unlinkat(dfd, base, 0);
int saved_errno = errno;
close(dfd);
if (rc != 0 && saved_errno != ENOENT) {
return errno_to_result("unlinkat", normalized);
}
return gm_ok_void();
}

Copilot is powered by AI and may make mistakes. Always verify output.
stack[depth++] =
(dir_stack_frame_t){.dir = child_dir, .path_len = current_len};
} else {
if (unlink(current) != 0 && errno != ENOENT) {

Check failure

Code scanning / CodeQL

Time-of-check time-of-use filesystem race condition High

The
filename
being operated upon was previously
checked
, but the underlying file may have been changed since then.
The
filename
being operated upon was previously
checked
, but the underlying file may have been changed since then.

Copilot Autofix

AI about 2 months ago

General fix:
Eliminate the window between stat (the check) and unlink (the use) by interacting with the file in an atomic fashion, using file descriptors instead of repeated name-based access. This avoids the risk of the file being replaced between the operations.

Detailed approach:

  • When encountering a non-directory file (the else block at line 605), instead of just calling unlink(current), open the file with open(current, O_NONBLOCK | O_NOFOLLOW | O_RDWR) (with O_NOFOLLOW to block symlink attacks). If the open succeeds, use unlinkat (or, on Linux, unlinkat with an open directory if possible) to remove the file via its file descriptor, ensuring it is the exact file just opened. Close the fd after.
  • Error handling should deal with open/unlink failures (including ELOOP for symlinks).
  • If O_NOFOLLOW and/or unlinkat are not available, document the limitation and fall back (with a warning) to the original code; on modern POSIX, we assume both are available.
  • Required: Addition of <fcntl.h> for open/O_NOFOLLOW, potentially <unistd.h> for unlinkat if not already included.

File/region:

  • Only modify the non-directory branch (lines 605–611) in core/src/adapters/fs/posix_temp_adapter.c.
  • Add any required includes at the top as permitted.

Suggested changeset 1
core/src/adapters/fs/posix_temp_adapter.c

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/core/src/adapters/fs/posix_temp_adapter.c b/core/src/adapters/fs/posix_temp_adapter.c
--- a/core/src/adapters/fs/posix_temp_adapter.c
+++ b/core/src/adapters/fs/posix_temp_adapter.c
@@ -6,6 +6,7 @@
 #include <dirent.h>
 #include <errno.h>
 #include <inttypes.h>
+#include <fcntl.h>
 #include <pwd.h>
 #include <stdbool.h>
 #include <stdint.h>
@@ -603,9 +604,34 @@
                 stack[depth++] =
                     (dir_stack_frame_t){.dir = child_dir, .path_len = current_len};
             } else {
-                if (unlink(current) != 0 && errno != ENOENT) {
-                    close_dir_stack(stack, depth);
-                    return errno_to_result("unlink", current);
+                int file_fd = open(current, O_RDWR | O_NONBLOCK | O_NOFOLLOW);
+                if (file_fd < 0) {
+                    if (errno == ELOOP) {
+                        // Refuse to unlink symlinks, treat as error or skip
+                        close_dir_stack(stack, depth);
+                        return gm_err_void(
+                            GM_ERROR(GM_ERR_INVALID_PATH,
+                                "refuse to unlink symlinked file"));
+                    } else if (errno != ENOENT) {
+                        close_dir_stack(stack, depth);
+                        return errno_to_result("open", current);
+                    }
+                    // ENOENT: already gone, skip
+                } else {
+#ifdef AT_FDCWD
+                    if (unlinkat(AT_FDCWD, current, 0) != 0 && errno != ENOENT) {
+                        close(file_fd);
+                        close_dir_stack(stack, depth);
+                        return errno_to_result("unlinkat", current);
+                    }
+#else
+                    if (unlink(current) != 0 && errno != ENOENT) {
+                        close(file_fd);
+                        close_dir_stack(stack, depth);
+                        return errno_to_result("unlink", current);
+                    }
+#endif
+                    close(file_fd);
                 }
                 current[parent_len] = '\0';
             }
EOF
@@ -6,6 +6,7 @@
#include <dirent.h>
#include <errno.h>
#include <inttypes.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdbool.h>
#include <stdint.h>
@@ -603,9 +604,34 @@
stack[depth++] =
(dir_stack_frame_t){.dir = child_dir, .path_len = current_len};
} else {
if (unlink(current) != 0 && errno != ENOENT) {
close_dir_stack(stack, depth);
return errno_to_result("unlink", current);
int file_fd = open(current, O_RDWR | O_NONBLOCK | O_NOFOLLOW);
if (file_fd < 0) {
if (errno == ELOOP) {
// Refuse to unlink symlinks, treat as error or skip
close_dir_stack(stack, depth);
return gm_err_void(
GM_ERROR(GM_ERR_INVALID_PATH,
"refuse to unlink symlinked file"));
} else if (errno != ENOENT) {
close_dir_stack(stack, depth);
return errno_to_result("open", current);
}
// ENOENT: already gone, skip
} else {
#ifdef AT_FDCWD
if (unlinkat(AT_FDCWD, current, 0) != 0 && errno != ENOENT) {
close(file_fd);
close_dir_stack(stack, depth);
return errno_to_result("unlinkat", current);
}
#else
if (unlink(current) != 0 && errno != ENOENT) {
close(file_fd);
close_dir_stack(stack, depth);
return errno_to_result("unlink", current);
}
#endif
close(file_fd);
}
current[parent_len] = '\0';
}
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
core/src/journal/reader.c (1)

482-499: Address the duplication flagged in previous review.

The monotonic_ms_now implementation is correct (proper Windows QueryPerformanceCounter, POSIX CLOCK_MONOTONIC, and fallback), but the duplication across reader.c and writer.c remains unresolved. Please refer to the existing review comment that suggests extracting this function to a timing utility module to satisfy the One-Thing rule.

🧹 Nitpick comments (3)
core/src/telemetry/log_format.c (2)

151-162: Add input validation to prevent undefined behavior.

sort_kvs does not validate that ordered is non-null or that count is reasonable. If a caller passes NULL or an uninitialized pointer, the loop will dereference invalid memory.

Apply this diff to add safety checks:

 static void sort_kvs(const gm_log_kv_t **ordered, size_t count) {
+    if (ordered == NULL || count == 0U) {
+        return;
+    }
     for (size_t entry_index = 1; entry_index < count; ++entry_index) {

176-181: Consider checked allocation for robustness.

While kv_count is bounded by MAX_TAGS_TOTAL in practice, the multiplication sizeof(*sorted) * kv_count could theoretically overflow with a very large count. Consider using a safer allocation pattern that checks for overflow.

Example approach:

+    if (kv_count > SIZE_MAX / sizeof(*sorted)) {
+        return gm_err_void(GM_ERROR(GM_ERR_OUT_OF_MEMORY,
+                                    "allocation size overflow"));
+    }
     const gm_log_kv_t **sorted =
         (const gm_log_kv_t **)malloc(sizeof(*sorted) * kv_count);
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1)

116-127: Simplify disposal to use the vtable consistently.

The special-case check for GmStderrDiagnosticsPortVtbl at line 120 is overly defensive. Since GmStderrDiagnosticsPortVtbl.dispose already points to dispose_impl, you can simplify by using the vtable consistently:

 void gm_stderr_diagnostics_port_dispose(gm_diagnostics_port_t *port) {
     if (port == NULL) {
         return;
     }
-    if (port->vtbl == &GmStderrDiagnosticsPortVtbl && port->self != NULL) {
-        dispose_impl(port->self);
-    } else if (port->vtbl != NULL && port->vtbl->dispose != NULL) {
+    if (port->vtbl != NULL && port->vtbl->dispose != NULL) {
         port->vtbl->dispose(port->self);
     }
     port->self = NULL;
     port->vtbl = NULL;
 }
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95d398a and 44f748c.

📒 Files selected for processing (5)
  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1 hunks)
  • core/src/adapters/fs/posix_temp_adapter.c (18 hunks)
  • core/src/journal/reader.c (7 hunks)
  • core/src/telemetry/config.c (7 hunks)
  • core/src/telemetry/log_format.c (3 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{c,cc,cpp,cxx,h,hpp,hxx,m,mm}

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER use NOLINT to suppress clang-tidy warnings; ALWAYS fix the underlying issue

Files:

  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/telemetry/config.c
  • core/src/journal/reader.c
  • core/src/telemetry/log_format.c
  • core/src/adapters/fs/posix_temp_adapter.c
**/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{c,h}: Use project-safe string/memory helpers: gm_snprintf, gm_strcpy_safe, gm_memcpy_span; check return codes; treat truncation as error
Zero output buffers on error paths before formatting or copying
Prefer GM_PATH_MAX; never use GM_PATH_MAX*2; allocate if larger is required
Language rules: C23, warnings-as-errors; no VLAs; no shadowing; explicit prototypes
Formatting follows .clang-format (LLVM-based, 4 spaces, 80 cols, right-aligned pointers); run clang-format
NEVER use NOLINT or other mechanisms to suppress clang-tidy warnings; fix the warnings instead
Naming: functions/vars lower_snake_case with gm_ prefix; macros UPPER_SNAKE; types end with t; header guards start with GITMIND
Includes: prefer specific headers and order/regroup per .clang-format
Equality is OID-first: when OIDs exist they must match; SHA fallback only when OIDs are absent

Files:

  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/telemetry/config.c
  • core/src/journal/reader.c
  • core/src/telemetry/log_format.c
  • core/src/adapters/fs/posix_temp_adapter.c
core/src/adapters/**

📄 CodeRabbit inference engine (AGENTS.md)

Runtime adapters live under core/src/adapters//_adapter.c with matching private headers; provide factory/teardown

Files:

  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/adapters/fs/posix_temp_adapter.c
core/src/**/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

core/src/**/*.{c,h}: Refs must be constructed via gm_build_ref and inputs starting with 'refs/' must be rejected per ref-name safety rules
Journal is append-only under refs/gitmind/edges/; cache under refs/gitmind/cache/ (no merges into cache)
Prefer OID-first APIs using git_oid (gm_oid_t) and compare via git_oid_cmp or gm-level helpers
Wrap all IO behind outbound ports; inject via gm_runtime_context_t; do not call external systems directly (e.g., libgit2) from domain/services
Maintain one responsibility per translation unit; split touched multi-concern files so the edited item lives alone (One-Thing rule)

Files:

  • core/src/adapters/diagnostics/stderr_diagnostics_adapter.c
  • core/src/telemetry/config.c
  • core/src/journal/reader.c
  • core/src/telemetry/log_format.c
  • core/src/adapters/fs/posix_temp_adapter.c
core/src/telemetry/*.{c,h}

📄 CodeRabbit inference engine (AGENTS.md)

Use the injected log formatter DI seam for structured logs; adapters emit via gm_logger_port

Files:

  • core/src/telemetry/config.c
  • core/src/telemetry/log_format.c
{core/src/adapters/fs/**/*.c,core/include/gitmind/ports/filesystem_port.h}

📄 CodeRabbit inference engine (AGENTS.md)

Use gm_fs_port for filesystem operations; no direct POSIX calls in domain/services

Files:

  • core/src/adapters/fs/posix_temp_adapter.c
🧠 Learnings (1)
📚 Learning: 2025-10-09T02:29:56.534Z
Learnt from: CR
PR: neuroglyph/git-mind#0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T02:29:56.534Z
Learning: Applies to {core/src/adapters/fs/**/*.c,core/include/gitmind/ports/filesystem_port.h} : Use gm_fs_port for filesystem operations; no direct POSIX calls in domain/services

Applied to files:

  • core/src/adapters/fs/posix_temp_adapter.c
🧬 Code graph analysis (4)
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (1)
core/tests/fakes/diagnostics/fake_diagnostics_port.c (2)
  • emit_impl (12-70)
  • dispose_impl (72-75)
core/src/telemetry/config.c (5)
core/include/gitmind/security/memory.h (1)
  • gm_memset_safe (47-59)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
core/src/crypto/backend.c (2)
  • gm_crypto_context_create (81-99)
  • gm_crypto_context_dispose (105-112)
core/src/error/error.c (1)
  • gm_error_free (161-178)
core/src/crypto/sha256.c (1)
  • gm_sha256_with_context (19-37)
core/src/journal/reader.c (3)
core/src/error/error.c (1)
  • gm_error_free (161-178)
core/include/gitmind/security/memory.h (1)
  • gm_memset_safe (47-59)
core/include/gitmind/util/memory.h (1)
  • gm_strcpy_safe (113-138)
core/src/adapters/fs/posix_temp_adapter.c (2)
core/include/gitmind/util/memory.h (2)
  • gm_strcpy_safe (113-138)
  • gm_memcpy_span (32-47)
core/src/error/error.c (1)
  • gm_error_free (161-178)
🪛 GitHub Check: CodeQL
core/src/adapters/fs/posix_temp_adapter.c

[failure] 521-521: Time-of-check time-of-use filesystem race condition
The filename being operated upon was previously checked, but the underlying file may have been changed since then.
The filename being operated upon was previously checked, but the underlying file may have been changed since then.


[failure] 606-606: Time-of-check time-of-use filesystem race condition
The filename being operated upon was previously checked, but the underlying file may have been changed since then.
The filename being operated upon was previously checked, but the underlying file may have been changed since then.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Compat Canaries (non-blocking) (clang-18)
  • GitHub Check: Compat Canaries (non-blocking) (gcc-12)
  • GitHub Check: Compat Canaries (non-blocking) (clang-19)
  • GitHub Check: Compat Canaries (non-blocking) (gcc-13)
  • GitHub Check: lint-and-sanitize
  • GitHub Check: build-test
🔇 Additional comments (21)
core/src/telemetry/log_format.c (2)

58-73: LGTM! Capacity check correctly accounts for null terminator.

The fix properly addresses the previous review by checking for JsonHexEscapeLength + 1U bytes before calling gm_snprintf, ensuring space for both the six-character escape sequence and the terminating null. The additional validation of hex_written against buffer bounds provides defense in depth.


223-267: LGTM! Proper resource cleanup on all error paths.

The function correctly releases the sorted entries array in all scenarios: success path (line 265), append overflow (line 250), and final capacity check (line 258). This prevents memory leaks regardless of the execution path.

core/src/telemetry/config.c (5)

27-35: LGTM! Cross-platform case-insensitive comparison properly implemented.

The wrapper correctly uses _stricmp on Windows and strcasecmp on POSIX platforms, ensuring portable ASCII case-insensitive string comparison across the codebase.


152-175: LGTM! Magic number eliminated per previous feedback.

The capacity check now correctly derives the array size from the struct definition (sizeof(cfg->extras) / sizeof(cfg->extras[0])), preventing divergence if the array size changes. The use of gm_memset_safe for error-path cleanup adds defense in depth.

Based on past review comments.


276-308: LGTM! Crypto context properly disposed on all paths.

The function correctly disposes the crypto context whether gm_sha256_with_context succeeds (lines 298-301) or fails (lines 291-294), and frees any disposal errors. The fallback to FNV on any crypto operation failure ensures robustness.


415-462: LGTM! Context-based tag builder correctly implements deterministic tag generation.

The new signature with gm_telemetry_tag_context_t cleanly separates concerns, and the builder pattern manages buffer bounds and tag limits effectively. The use of GM_PATH_MAX for the repo value buffer and GM_TRY for error propagation aligns with coding guidelines.

As per coding guidelines.


248-261: LGTM! Improved bounds checking prevents overflow.

The explicit available calculation and the check that wrote < available ensure the buffer is never overrun. The early return for empty keys/values avoids unnecessary formatting.

core/src/journal/reader.c (3)

28-30: LGTM!

The Windows header inclusion is properly guarded for platform-specific timing support, the new telemetry log format include is appropriate, and the forward declaration follows best practices.

Also applies to: 38-38, 42-42


149-158: LGTM!

The updated CBOR decoding API usage demonstrates proper error handling: zeroing message_len on failure, extracting the error code with a safe fallback, and freeing the error structure. This aligns with the coding guidelines for error path handling.


336-361: LGTM!

The canonicalization logic correctly updates to the new API with proper view handling:

  • Variable rename to canon_view better reflects that it's a view rather than a copy
  • gm_strcpy_safe return value is properly checked and truncation is treated as an error per coding guidelines
  • gm_memset_safe is correctly used to zero repo_id on error paths
  • Error handling is thorough with proper freeing throughout
core/src/adapters/diagnostics/stderr_diagnostics_adapter.c (7)

4-14: LGTM! Includes support the new functionality.

The new includes (ctype.h, stdbool.h, diagnostic_port.h, security/string.h) properly support the character escaping logic, boolean state field, and disposal infrastructure.


16-18: LGTM! State field is now meaningful and properly typed.

The emit_enabled boolean addresses the previous feedback about the unused field and serves a clear purpose in gating diagnostic emission.


20-58: LGTM! Escaping logic follows safe string practices.

The emit_escaped function properly:

  • Handles NULL input
  • Uses unsigned char cast to avoid sign-extension issues with iscntrl
  • Validates gm_snprintf return value before using the buffer
  • Correctly sizes the hex escape buffer (5 bytes for \xNN\0)

66-88: LGTM! Emission logic properly uses escaped output.

The emit_enabled check, NULL handling for component/event, and use of emit_escaped for all string output are correct.


90-92: LGTM! Disposal implementation is correct.

Simple and correct: frees the calloc'd state. This addresses the previous feedback about missing cleanup.


94-97: LGTM! Vtable correctly wires disposal.

The vtable properly includes the dispose callback, enabling proper cleanup via the port abstraction.


99-114: LGTM! Initialization properly allocates and configures state.

The function correctly:

  • Validates input
  • Allocates state with calloc
  • Initializes emit_enabled to true
  • Wires the vtable and state into the port
  • Returns an error on allocation failure
core/src/adapters/fs/posix_temp_adapter.c (4)

33-37: LGTM: Cross-platform strtok_r compatibility.

The macro correctly handles Windows (strtok_s) vs. POSIX (strtok_r) differences and is properly undefined at the end of the file (lines 817-819).


92-176: LGTM: Safer incremental directory creation.

The refactored implementation correctly:

  • Normalizes the path before processing
  • Uses strtok_r for clean segment iteration
  • Employs project-safe string helpers (gm_strcpy_safe, gm_memcpy_span) per coding guidelines
  • Handles the stat/mkdir race condition by checking for EEXIST
  • Validates buffer capacity at each step

As per coding guidelines.


667-711: LGTM: Well-designed canonicalization strategy.

The three helper functions correctly implement distinct canonicalization modes:

  1. canonicalize_existing_path: Uses realpath() for full physical resolution (follows symlinks, verifies existence)
  2. canonicalize_create_ok_path: Clever approach that canonicalizes the parent directory and appends the basename, allowing the final component to not exist yet
  3. canonicalize_logical_path: Pure logical normalization without filesystem interaction

This design properly separates concerns and handles the common use case where you need a canonical path for a file that doesn't exist yet but whose parent directory does.


98-114: LGTM: Exemplary string safety practices.

Throughout the file, you consistently use project-safe string helpers (gm_strcpy_safe, gm_memcpy_span, gm_snprintf) with proper return value checking and buffer overflow protection. The code properly:

  • Checks return values from all string operations
  • Validates buffer capacity before operations
  • Returns descriptive errors on overflow (e.g., GM_ERR_PATH_TOO_LONG)
  • Zeros buffers on error paths where appropriate

This is a model implementation of the string safety requirements in the coding guidelines.

As per coding guidelines.

Also applies to: 151-157, 181-195, 299-301, 531-535, 569-576

Comment on lines +63 to +65
if (kv_count > 0U && kvs == NULL) {
return gm_ok_void();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Return an error for invalid arguments (inconsistent with test fake).

The code silently succeeds when kvs == NULL but kv_count > 0, which is inconsistent with the fake diagnostics port that returns GM_ERR_INVALID_ARGUMENT. This breaks test/production parity and could hide caller bugs.

Apply this diff to align with the fake port behavior:

     if (kv_count > 0U && kvs == NULL) {
-        return gm_ok_void();
+        return gm_err_void(GM_ERROR(GM_ERR_INVALID_ARGUMENT,
+                                    "diagnostics kv list missing"));
     }
🤖 Prompt for AI Agents
In core/src/adapters/diagnostics/stderr_diagnostics_adapter.c around lines
63–65, the function currently returns success when kv_count > 0 and kvs == NULL;
change this to return an invalid-argument error to match the fake diagnostics
port. Detect the invalid argument case (kv_count > 0 && kvs == NULL) and return
gm_err(GM_ERR_INVALID_ARGUMENT) (or the project's equivalent error constructor)
instead of gm_ok_void(), ensuring callers/tests see the same error behavior as
the fake implementation.

Comment on lines +28 to +31
#ifndef GM_HAVE_REALPATH_DECL
#define GM_HAVE_REALPATH_DECL 1
extern char *realpath(const char *path, char *resolved_path);
#endif
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unconventional feature detection: realpath declaration guard defaults to 1.

Feature detection macros should typically be defined by the build system (e.g., CMake, autoconf) only when a feature is confirmed absent. Defaulting GM_HAVE_REALPATH_DECL to 1 assumes the declaration is always missing, which is backwards for most modern POSIX systems where realpath is declared in <stdlib.h>.

Consider this approach instead:

-#ifndef GM_HAVE_REALPATH_DECL
-#define GM_HAVE_REALPATH_DECL 1
+#ifdef GM_HAVE_REALPATH_DECL
 extern char *realpath(const char *path, char *resolved_path);
 #endif

Then, configure your build system to define GM_HAVE_REALPATH_DECL only on platforms where the declaration is genuinely missing.

📝 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
#ifndef GM_HAVE_REALPATH_DECL
#define GM_HAVE_REALPATH_DECL 1
extern char *realpath(const char *path, char *resolved_path);
#endif
#ifdef GM_HAVE_REALPATH_DECL
extern char *realpath(const char *path, char *resolved_path);
#endif
🤖 Prompt for AI Agents
In core/src/adapters/fs/posix_temp_adapter.c around lines 28 to 31, the code
unconditionally defines GM_HAVE_REALPATH_DECL to 1 and declares realpath, which
assumes the declaration is missing; remove the default definition and only emit
the extern declaration when the build system sets GM_HAVE_REALPATH_DECL (e.g.,
wrap the declaration in #if GM_HAVE_REALPATH_DECL / #endif or check
defined(GM_HAVE_REALPATH_DECL) and ensure the project build configuration
(CMake/autoconf) defines GM_HAVE_REALPATH_DECL only on platforms where realpath
is actually missing so the header declaration is not redundantly injected on
systems with a native declaration.

Comment on lines 497 to 629
static gm_result_void_t remove_tree_impl(const char *path) {
if (path == NULL) {
return gm_ok_void();
}
// Open parent directory
int dirfd = AT_FDCWD;
// For absolute path, just use AT_FDCWD
// For relative, this will also work as AT_FDCWD
return remove_tree_at(dirfd, path);
}

static gm_result_void_t remove_tree_at(int dirfd, const char *name) {
struct stat st = {0};
// Use fstatat to check stat info relative to dirfd
if (fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW) != 0) {
char normalized[GM_PATH_MAX];
gm_result_void_t normalize_rc =
gm_fs_path_normalize_logical(path, normalized, sizeof(normalized));
if (!normalize_rc.ok) {
return normalize_rc;
}
if (normalized[0] == '\0') {
return gm_ok_void();
}

struct stat root_stat = {0};
if (stat(normalized, &root_stat) != 0) {
if (errno == ENOENT) {
return gm_ok_void();
}
return gm_err_void(GM_ERROR(GM_ERR_IO_FAILED,
"fstatat failed for %s: %s", name,
strerror(errno)));
return errno_to_result("stat", normalized);
}

if (!S_ISDIR(st.st_mode)) {
// Unlink regular file or symlink or device, etc.
if (unlinkat(dirfd, name, 0) != 0) {
if (errno == ENOENT) {
return gm_ok_void();
}
return gm_err_void(
GM_ERROR(GM_ERR_IO_FAILED, "unlinkat failed for %s: %s", name,
strerror(errno)));
if (!S_ISDIR(root_stat.st_mode)) {
if (unlink(normalized) != 0 && errno != ENOENT) {
return errno_to_result("unlink", normalized);
}
return gm_ok_void();
}

// Open the directory itself
int subdirfd = openat(dirfd, name, O_RDONLY | O_DIRECTORY);
if (subdirfd < 0) {
dir_stack_frame_t stack[GM_PATH_MAX];
size_t depth = 0U;

char current[GM_PATH_MAX];
if (gm_strcpy_safe(current, sizeof(current), normalized) != GM_OK) {
return gm_err_void(
GM_ERROR(GM_ERR_IO_FAILED, "openat (directory) failed for %s: %s", name,
strerror(errno)));
GM_ERROR(GM_ERR_PATH_TOO_LONG,
"normalized path exceeds buffer while removing dir"));
}
size_t current_len = strlen(current);

DIR *dir = fdopendir(subdirfd);
if (dir == NULL) {
close(subdirfd); // Make sure to close the fd
return gm_err_void(
GM_ERROR(GM_ERR_IO_FAILED, "fdopendir failed for %s: %s", name,
strerror(errno)));
DIR *root_dir = opendir(current);
if (root_dir == NULL) {
return errno_to_result("opendir", current);
}
stack[depth++] = (dir_stack_frame_t){.dir = root_dir, .path_len = current_len};

struct dirent *entry = NULL;
while ((entry = readdir(dir)) != NULL) {
if (is_dot_entry(entry)) {
continue;
}
while (depth > 0U) {
dir_stack_frame_t *frame = &stack[depth - 1U];
struct dirent *entry = readdir(frame->dir);
if (entry != NULL) {
if (is_dot_entry(entry)) {
continue;
}

// Call recursively with subdirfd as dirfd and entry->d_name
gm_result_void_t child_result = remove_tree_at(subdirfd, entry->d_name);
if (!child_result.ok) {
closedir(dir); // This closes subdirfd as well
return child_result;
}
}
size_t parent_len = frame->path_len;
bool needs_separator =
(parent_len > 0U && current[parent_len - 1U] != '/');
size_t name_len = strlen(entry->d_name);
size_t required = parent_len + (needs_separator ? 1U : 0U) +
name_len + 1U;
if (required >= sizeof(current)) {
close_dir_stack(stack, depth);
return gm_err_void(
GM_ERROR(GM_ERR_PATH_TOO_LONG,
"child path exceeds buffer"));
}

closedir(dir); // This closes subdirfd
// Remove the directory itself. Use unlinkat with AT_REMOVEDIR.
if (unlinkat(dirfd, name, AT_REMOVEDIR) != 0) {
if (errno == ENOENT) {
return gm_ok_void();
size_t append_pos = parent_len;
if (needs_separator) {
current[append_pos++] = '/';
}
if (gm_memcpy_span(current + append_pos,
sizeof(current) - append_pos, entry->d_name,
name_len) != 0) {
close_dir_stack(stack, depth);
return gm_err_void(
GM_ERROR(GM_ERR_PATH_TOO_LONG,
"child path exceeds buffer"));
}
current_len = append_pos + name_len;
current[current_len] = '\0';

struct stat entry_stat = {0};
if (stat(current, &entry_stat) != 0) {
if (errno == ENOENT) {
current[parent_len] = '\0';
continue;
}
close_dir_stack(stack, depth);
return errno_to_result("stat", current);
}

if (S_ISDIR(entry_stat.st_mode)) {
DIR *child_dir = opendir(current);
if (child_dir == NULL) {
close_dir_stack(stack, depth);
return errno_to_result("opendir", current);
}
if (depth >= (sizeof(stack) / sizeof(stack[0]))) {
closedir(child_dir);
close_dir_stack(stack, depth);
return gm_err_void(
GM_ERROR(GM_ERR_INVALID_PATH,
"directory depth exceeds stack capacity"));
}
stack[depth++] =
(dir_stack_frame_t){.dir = child_dir, .path_len = current_len};
} else {
if (unlink(current) != 0 && errno != ENOENT) {
close_dir_stack(stack, depth);
return errno_to_result("unlink", current);
}
current[parent_len] = '\0';
}
} else {
closedir(frame->dir);
depth--;

if (rmdir(current) != 0 && errno != ENOENT) {
close_dir_stack(stack, depth);
return errno_to_result("rmdir", current);
}

if (depth > 0U) {
dir_stack_frame_t *parent = &stack[depth - 1U];
current[parent->path_len] = '\0';
}
}
return gm_err_void(
GM_ERROR(GM_ERR_IO_FAILED, "unlinkat (rmdir) failed for %s: %s", name,
strerror(errno)));
}

return gm_ok_void();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix TOCTOU vulnerabilities in file removal.

CodeQL correctly identifies time-of-check-time-of-use race conditions at lines 521 and 606. Between the stat() check and the unlink() call, an attacker could replace the file with a symlink to a critical system file, causing unintended deletion.

As per coding guidelines, fix the warnings rather than suppress them.

Recommended fix: Remove the stat() checks before unlink() and rely on error codes to differentiate files from directories:

-            struct stat entry_stat = {0};
-            if (stat(current, &entry_stat) != 0) {
-                if (errno == ENOENT) {
-                    current[parent_len] = '\0';
-                    continue;
-                }
-                close_dir_stack(stack, depth);
-                return errno_to_result("stat", current);
-            }
-
-            if (S_ISDIR(entry_stat.st_mode)) {
+            /* Try to remove as file first; if EISDIR, it's a directory */
+            if (unlink(current) == 0 || errno == ENOENT) {
+                current[parent_len] = '\0';
+                continue;
+            }
+            
+            if (errno == EISDIR) {
+                /* It's a directory, traverse it */
                 DIR *child_dir = opendir(current);
                 if (child_dir == NULL) {
                     close_dir_stack(stack, depth);
@@ -603,11 +590,9 @@
                 stack[depth++] =
                     (dir_stack_frame_t){.dir = child_dir, .path_len = current_len};
             } else {
-                if (unlink(current) != 0 && errno != ENOENT) {
-                    close_dir_stack(stack, depth);
-                    return errno_to_result("unlink", current);
-                }
-                current[parent_len] = '\0';
+                /* unlink failed with error other than EISDIR */
+                close_dir_stack(stack, depth);
+                return errno_to_result("unlink", current);
             }

Similarly, for the root path handling (lines 520-525), use the same approach:

-    if (!S_ISDIR(root_stat.st_mode)) {
-        if (unlink(normalized) != 0 && errno != ENOENT) {
-            return errno_to_result("unlink", normalized);
-        }
+    if (!S_ISDIR(root_stat.st_mode)) {
+        /* Non-directory: try to unlink */
+        if (unlink(normalized) == 0 || errno == ENOENT) {
+            return gm_ok_void();
+        }
+        return errno_to_result("unlink", normalized);
-        return gm_ok_void();
     }

This eliminates the TOCTOU window and still correctly handles both files and directories.

As per coding guidelines.

🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 521-521: Time-of-check time-of-use filesystem race condition
The filename being operated upon was previously checked, but the underlying file may have been changed since then.
The filename being operated upon was previously checked, but the underlying file may have been changed since then.


[failure] 606-606: Time-of-check time-of-use filesystem race condition
The filename being operated upon was previously checked, but the underlying file may have been changed since then.
The filename being operated upon was previously checked, but the underlying file may have been changed since then.

🤖 Prompt for AI Agents
In core/src/adapters/fs/posix_temp_adapter.c around lines 497–629, the code
performs stat() checks before unlink() (root path around lines ~520–525 and
per-entry around ~606) creating TOCTOU windows; remove those pre-unlink stat()
calls and instead attempt the destructive operation and branch on error codes:
for the root path, try unlink(normalized) and if it fails with EISDIR (or
returns EISDIR when trying to unlink a directory) then open the path as a
directory (opendir) and proceed with the directory-walking stack; for child
entries, replace the stat-before-unlink logic with trying unlink(current) and if
unlink fails with EISDIR then treat it as a directory (opendir and push to
stack), if unlink fails with ENOENT just continue, and for other errors return
errno_to_result; ensure you still handle path-too-long and buffer updates as
before and close directory handles on error.

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