Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b9c113f
refactor(knowledge): tidy video-digest adapters and liveness comments…
claude Sep 3, 2026
45e5645
refactor(knowledge): drop dead acquisition state and dedupe its test …
claude Sep 3, 2026
c356b49
refactor(knowledge): unwrap an empty try/finally in the watch runtime…
claude Sep 3, 2026
0c2598e
refactor(knowledge): name the transcript paragraph count, drop plan r…
claude Sep 3, 2026
46c9df5
chore(knowledge): bump to 0.13.40 for the video-digest tidy wave
claude Sep 3, 2026
8353764
Merge remote-tracking branch 'origin/main' into claude/hello-yuwqst
claude Sep 3, 2026
e844444
fix(machine-health): pin the test runner to Pester v5 instead of newe…
claude Sep 3, 2026
6477d1c
chore(machine-health): bump to 0.12.11 for the Pester v5 runner pin
claude Sep 3, 2026
9646857
refactor(ai-briefing): drop dead deck-emitter options and name the se…
claude Sep 3, 2026
d42934d
fix(claude-memory): isolate ambient CLAUDE_CONFIG_DIR in two audit su…
claude Sep 3, 2026
2a3dfb4
chore: bump ai-briefing to 0.7.23 and claude-memory to 0.11.15
claude Sep 3, 2026
f275aad
refactor(miro): type the board payloads and hoist an overlap loop inv…
claude Sep 3, 2026
359a964
chore(miro): bump to 0.3.14 for the board-payload typing wave
claude Sep 3, 2026
50f6be3
perf(claude-ops): read three audit flags from one jq process instead …
claude Sep 3, 2026
2f2a364
refactor(autonomy): name the embedded-v4 extraction and drop an unrea…
claude Sep 3, 2026
e29bde8
refactor(disk-hygiene): drop a dead test stub and flatten a platform …
claude Sep 3, 2026
94d877a
chore: bump claude-ops 0.41.10, autonomy 0.22.23, disk-hygiene 0.21.5
claude Sep 3, 2026
37646ec
fix(knowledge): stop fetch-deck-attachments emitting doubled newlines…
claude Sep 3, 2026
977df98
refactor(session-flow): drop no-op match arms and fix a pre-existing …
claude Sep 3, 2026
d17bc13
chore: bump session-flow to 0.34.23, extend knowledge 0.13.40
claude Sep 3, 2026
86aa9f2
docs(kindle-dedrm): normalize the last PowerShell comment em dashes (…
claude Sep 3, 2026
0cde897
chore(kindle-dedrm): bump to 0.7.13 for the comment-only tidy group
claude Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/ai-briefing/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "ai-briefing",
"version": "0.7.22",
"version": "0.7.23",
"description": "Build source-backed AI-industry briefings from official vendor publications, configured RSS/Atom feeds, GitHub releases, reputable secondary reporting, and user-supplied URLs. Deduplicate, rank, and present results as markdown or optional HTML/PPTX decks, with repository-owned profile, audience, and brand configuration. Automated X/Twitter collection is disabled; Playwright is used only for deterministic local rendering.",
"author": {
"name": "Melodic Software",
Expand Down
18 changes: 18 additions & 0 deletions plugins/ai-briefing/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@
All notable changes to the `ai-briefing` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.7.23]

### Changed

- **generate:** deck-emitter tidyings from the repo-wide sweep. `build-pptx.js`
drops two `decorate()` options no call site ever set (both defaulted to true,
so the footer is now unconditional and the logo test reduces to the data check
it already depended on), corrects a header that named a fixed output file where
the emitter writes `ai-meeting-{N}.pptx`, and unwraps a template literal around
an already-string expression. `build-sections.js` drops an unreachable
disjunct from a tier guard, leaving the empty-tier early return intact.
`emit-slides-data.js` names its inline seen-items map build
`firstSeenDatesByUrl()`. `validate.js` takes a concise arrow body and corrects
a comment that misnamed the consumer of its `total` field. Emitted output is
byte-identical: HTML, `slides-data.js`, all 257 PPTX zip entries across both
logo variants, the PDF text layer and `audit.json` all match before and after,
with the node:test suite at 41 passed.

## [0.7.22]

### Changed
Expand Down
30 changes: 14 additions & 16 deletions plugins/ai-briefing/skills/generate/output/build/build-pptx.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Build ai-meeting-1.pptx from slides-data.js using pptxgenjs.
// Build ai-meeting-{N}.pptx from slides-data.js using pptxgenjs.
// Theme: dark navy + electric blue + amber. Strategy: Team All-Hands arc.
import PptxGenJS from "pptxgenjs";
import path from "node:path";
Expand Down Expand Up @@ -31,7 +31,7 @@ const FONT_HEAD = theme.pptFontHead;
const FONT_BODY = theme.pptFontBody;

// Helper: solid bg + accent strip + footer
function decorate(slide, { showFooter = true, eyebrow = null, showLogo = true } = {}) {
function decorate(slide, { eyebrow = null } = {}) {
slide.background = { color: theme.bg };

// Top accent bar (brand red)
Expand All @@ -55,26 +55,24 @@ function decorate(slide, { showFooter = true, eyebrow = null, showLogo = true }
}

// Top-right logo (white)
if (showLogo && logoWhiteData) {
if (logoWhiteData) {
slide.addImage({
data: logoWhiteData,
x: W - 1.65, y: 0.2, w: 1.3, h: 0.36,
sizing: { type: "contain", w: 1.3, h: 0.36 },
});
}

if (showFooter) {
slide.addText([
{ text: meta.org, options: { color: theme.textMuted, fontSize: 9 } },
{ text: " · ", options: { color: theme.divider, fontSize: 9 } },
{ text: `AI Meeting #${meta.meetingNumber}`, options: { color: theme.textMuted, fontSize: 9 } },
{ text: " · ", options: { color: theme.divider, fontSize: 9 } },
{ text: meta.date, options: { color: theme.textMuted, fontSize: 9 } },
], {
x: 0.5, y: H - 0.4, w: W - 1, h: 0.3,
fontFace: FONT_BODY, align: "left",
});
}
slide.addText([
{ text: meta.org, options: { color: theme.textMuted, fontSize: 9 } },
{ text: " · ", options: { color: theme.divider, fontSize: 9 } },
{ text: `AI Meeting #${meta.meetingNumber}`, options: { color: theme.textMuted, fontSize: 9 } },
{ text: " · ", options: { color: theme.divider, fontSize: 9 } },
{ text: meta.date, options: { color: theme.textMuted, fontSize: 9 } },
], {
x: 0.5, y: H - 0.4, w: W - 1, h: 0.3,
fontFace: FONT_BODY, align: "left",
});
}

// Hyperlinked source URLs as one text run per URL, separated by a small blank line.
Expand Down Expand Up @@ -190,7 +188,7 @@ function buildAgenda(s, slide) {
fill: { color: theme.bgAccent }, line: { color: theme.divider, width: 0.5 },
rectRadius: 0.08,
});
slide.addText(`${(i + 1).toString().padStart(2, "0")}`, {
slide.addText((i + 1).toString().padStart(2, "0"), {
x: x + 0.15, y, w: 0.7, h: rowH,
fontFace: FONT_HEAD, fontSize: 18, bold: true,
color: theme.accent, valign: "middle",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ function renderNewsSection(sectionKey, sectionSlides, providerLogoSvg) {

const tierBlock = (tier, heading) => {
const bullets = byTier[tier];
if (!bullets || bullets.length === 0) return "";
if (bullets.length === 0) return "";
return `
<div class="tier tier-${tier}">
<h3 class="tier-heading">${heading}</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@ async function readState() {
}
}

/** URL → first-seen UTC timestamp, read from an already-loaded seen-items state.
* This is the final fallback the briefing parser reaches for when an item
* carries no date of its own. */
function firstSeenDatesByUrl(state) {
const dates = new Map();
for (const it of state?.items || []) {
if (!it.url || !it.first_seen) continue;
const m = it.first_seen.match(/^(\d{4})-(\d{2})-(\d{2})/);
if (m) {
dates.set(
it.url,
Date.UTC(parseInt(m[1], 10), parseInt(m[2], 10) - 1, parseInt(m[3], 10)),
);
}
}
return dates;
}

function asJsLiteral(v) {
return JSON.stringify(v, null, 2);
}
Expand Down Expand Up @@ -101,22 +119,7 @@ async function main() {
// exist yet on a fresh install.
await fs.mkdir(buildOutDir(), { recursive: true });

// Build seen-items.json URL → first_seen map (final date-inference fallback).
// Reuse the `state` read above — seen-items.json is unchanged between reads.
const seenUrlDateMap = new Map();
if (state?.items) {
for (const it of state.items) {
if (it.url && it.first_seen) {
const m = it.first_seen.match(/^(\d{4})-(\d{2})-(\d{2})/);
if (m) {
seenUrlDateMap.set(
it.url,
Date.UTC(parseInt(m[1], 10), parseInt(m[2], 10) - 1, parseInt(m[3], 10)),
);
}
}
}
}
const seenUrlDateMap = firstSeenDatesByUrl(state);
const briefing = await parseBriefing(briefingPath, { seenUrlDateMap });
console.log(`Seen-items dates: ${seenUrlDateMap.size} URL→date entries (final fallback)`);

Expand Down
14 changes: 6 additions & 8 deletions plugins/ai-briefing/skills/generate/output/build/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ await page.waitForSelector("main#deck");
await page.evaluate(settleRender);

// Section-based audit (sectioned-scroll deck). One screenshot per <section>.
// Page-wide URL/headline coverage check (vs old per-slide approach).
const total = slides.length; // legacy field name — used downstream for PDF page check
// Page-wide URL/headline coverage check.
const total = slides.length; // the audit.json `total` key; the PPTX gate compares against it
const sectionInfo = await page.evaluate(() => {
const sections = [...document.querySelectorAll("main#deck > section.section")];
return sections.map((s) => ({ id: s.id, h1: s.querySelector("h1")?.innerText.trim() || "", h2: s.querySelector("h2")?.innerText.trim() || "" }));
Expand All @@ -121,12 +121,10 @@ for (let i = 0; i < sectionInfo.length; i++) {

// Page-wide DOM extraction — section grouping concatenates bullets so
// per-slide structure is gone. Verify URL + headline coverage at page level.
const pageData = await page.evaluate(() => {
return {
urls: Array.from(document.querySelectorAll(".news-url")).map((a) => a.href),
headlines: Array.from(document.querySelectorAll(".news-headline, .flair-headline, .pattern-headline")).map((s) => s.innerText.trim()),
};
});
const pageData = await page.evaluate(() => ({
urls: Array.from(document.querySelectorAll(".news-url")).map((a) => a.href),
headlines: Array.from(document.querySelectorAll(".news-headline, .flair-headline, .pattern-headline")).map((s) => s.innerText.trim()),
}));
pageData.sectionOverflows = await page.evaluate(collectSectionOverflows);

await browser.close();
Expand Down
2 changes: 1 addition & 1 deletion plugins/autonomy/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "autonomy",
"version": "0.22.22",
"version": "0.22.23",
"description": "Governed autonomous agent operation: role-topology, binding-seam, wiring-vs-advisor, telemetry, return-accounting, trigger-dispatch, per-work-class guardrail-matrix, standing-routine-catalog, and design-only runner-charter contracts for climbing the AI-adoption ladder, plus a guided-setup skill that discovers an adopting org's state, writes its schema-versioned binding, wires standards-pinned OTLP emission with a zero-cost file-artifact default, wires human-attested return capture at the task boundary, wires signal adapters with one governed dispatch entrypoint, binds the five-class guardrail matrix to an org's isolation substrates with an in-boundary live-validation probe before recording each fail-closed binding, and stands up standing-routine-catalog classes as scheduled temporal signal adapters behind the one governed queue with free scheduling defaults wired as reviewable changes and each routine's work-class mapping homed on the security surface.",
"author": {
"name": "Melodic Software",
Expand Down
18 changes: 18 additions & 0 deletions plugins/autonomy/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@
All notable changes to the `autonomy` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.22.23]

### Changed

- **Setup-script tidyings from the repo-wide sweep.**
`check-security-binding.mjs` names the repeated embedded-IPv4 extraction inside
`isNonExternalEgressHost` as `embeddedV4()`, routes two inline comma-splits
through the file's own `splitRecordedList`, and renames an inner `aiReview`
that shadowed a binding declared 55 lines above.
`resolve-prerequisites.mjs` drops a parameter `probeMergePath` never read.
`apply-prerequisite-resolution.mjs` loses a comment that said less than the
guard beneath it. Two test files were reindented by the repo's own `shfmt`
hook. The lane-stop-gate protocol was frozen for this sweep and is untouched.
Equivalence for the address predicate was checked over a 183-address corpus
spanning v4-mapped, NAT64, loopback, link-local, ULA, 6to4, Teredo and 40
malformed forms, with zero divergences; suites green at 90, 11, 6, 4, 544
and 22 checks.

## [0.22.22]

### Changed
Expand Down
32 changes: 16 additions & 16 deletions plugins/autonomy/hooks/lane-stop-gate.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -810,11 +810,11 @@ elif ! command -v timeout >/dev/null 2>&1 ||
! timeout --help 2>&1 | grep -Fq -- '--kill-after'; then
ok "GNU timeout unavailable; skip non-regular claim path hang case"
else
FIFO_INPUT=$(build_input Stop "no token" false "" "sess-fifo")
FIFO_OUT="$WORK/fifo-out"
FIFO_RC=0
# shellcheck disable=SC2016 # bash -c program is single-quoted; \$1..\$4 expand in the child
if timeout 5 bash -c '
FIFO_INPUT=$(build_input Stop "no token" false "" "sess-fifo")
FIFO_OUT="$WORK/fifo-out"
FIFO_RC=0
# shellcheck disable=SC2016 # bash -c program is single-quoted; \$1..\$4 expand in the child
if timeout 5 bash -c '
cd "$1" && printf "%s" "$2" |
env -u CLAUDE_PLUGIN_OPTION_LANE_STOP_GATE_ENABLED \
-u CLAUDE_PLUGIN_OPTION_LANE_STOP_GATE_SENTINEL \
Expand All @@ -824,17 +824,17 @@ if timeout 5 bash -c '
CLAUDE_PLUGIN_OPTION_LANE_STOP_GATE_ARM_ID="$3" \
bash "$4" 2>/dev/null
' _ "$UNRELATED" "$FIFO_INPUT" "$ARM_ID_10" "$HOOK" >"$FIFO_OUT" 2>/dev/null; then
FIFO_RC=0
else
FIFO_RC=$?
fi
if [[ "$FIFO_RC" -eq 124 ]]; then
fail "a planted FIFO at the claim path hung the Stop hook"
elif is_block "$(cat "$FIFO_OUT" 2>/dev/null)"; then
ok "a non-regular file at the claim path neither hangs the hook nor loses the gate"
else
fail "a planted FIFO at the claim path left the lane ungated"
fi
FIFO_RC=0
else
FIFO_RC=$?
fi
if [[ "$FIFO_RC" -eq 124 ]]; then
fail "a planted FIFO at the claim path hung the Stop hook"
elif is_block "$(cat "$FIFO_OUT" 2>/dev/null)"; then
ok "a non-regular file at the claim path neither hangs the hook nor loses the gate"
else
fail "a planted FIFO at the claim path left the lane ungated"
fi
fi

# ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ function writeRatified(repo, proposal) {
],
declarations: merged,
};
// Refuse security keys.
if (existing.admission || existing.isolation || existing.executor_class) {
throw new Error("refusing to write security-binding axes into autonomy binding");
}
Expand Down
14 changes: 8 additions & 6 deletions plugins/autonomy/skills/setup/scripts/check-security-binding.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1173,9 +1173,11 @@ function isNonExternalEgressHost(host) {
const hextets = expandIpv6(h);
// An invalid literal is not a valid probe target — reject outright.
if (hextets === null) return true;
// The IPv4 address the last two hextets carry, for the forms that embed one.
const embeddedV4 = () => Number.parseInt(hextets[6], 16) * 65536 + Number.parseInt(hextets[7], 16);
if (hextets.slice(0, 5).every((hextet) => hextet === "0000") && hextets[5] === "ffff") {
// v4-mapped: classify the embedded IPv4.
return isDeniedV4(Number.parseInt(hextets[6], 16) * 65536 + Number.parseInt(hextets[7], 16));
return isDeniedV4(embeddedV4());
}
if (hextets.slice(0, 6).every((hextet) => hextet === "0000")) {
// ::/96 — unspecified (::), loopback (::1), and the whole deprecated
Expand All @@ -1190,7 +1192,7 @@ function isNonExternalEgressHost(host) {
// flags it globally reachable, but RFC 6052 forbids embedding non-global
// IPv4, so classify the embedded address like the v4-mapped branch above.
if (hextets[0] === "0064" && hextets[1] === "ff9b" && hextets.slice(2, 6).every((hextet) => hextet === "0000")) {
return isDeniedV4(Number.parseInt(hextets[6], 16) * 65536 + Number.parseInt(hextets[7], 16));
return isDeniedV4(embeddedV4());
}
const first = Number.parseInt(hextets[0], 16);
const second = Number.parseInt(hextets[1], 16);
Expand Down Expand Up @@ -1385,7 +1387,7 @@ function verifyProbeTranscript(ref, probeRoot, surfaceId, level, substrate, subs
// one), and every entry is validated INDEPENDENTLY below — classifying the
// unsplit string would reject a genuine multi-target capture as one
// invalid DNS name. Empty entries (leading/trailing/double commas) reject.
const egressHosts = rawEgressHost.split(",").map((host) => host.trim());
const egressHosts = splitRecordedList(rawEgressHost);
for (const host of egressHosts) {
if (host.length === 0 || /\s/.test(host)) {
return `transcript ${path} records assertions.egress_denied.host ${JSON.stringify(rawEgressHost)} — every comma-separated host entry must be non-empty with no whitespace`;
Expand Down Expand Up @@ -1477,7 +1479,7 @@ function verifyProbeTranscript(ref, probeRoot, surfaceId, level, substrate, subs
// (readable) path leaks. Every path entry must be recognizably a
// host-credential location (case-insensitive, path-separator-agnostic,
// matched per component).
const credentialPaths = transcript.assertions.credentials_absent.path.split(",").map((entry) => entry.trim());
const credentialPaths = splitRecordedList(transcript.assertions.credentials_absent.path);
const rawCredentialCodes = transcript.assertions.credentials_absent.exit_code;
const credentialCodes = splitRecordedList(rawCredentialCodes);
if (credentialCodes.length !== credentialPaths.length) {
Expand Down Expand Up @@ -1965,8 +1967,8 @@ function checkSemantics(binding, probeRoot, egressAllowList, credentialRoots) {
`merge_policy.${workClass}: "auto" with verification_blocking.${layer}.${workClass} "advisory" — an automatic transition requires unanimous agreement among every checker the class declares, and an advisory layer records a checker's dissent without withholding the transition; set verification_blocking.${layer}.${workClass} to "blocking" (ratifying its promotion_state cell where the cell is promotable) or bind merge_policy.${workClass} to "human"`,
);
}
const aiReview = binding.verification_blocking["ai-review"];
if (!isPlainObject(aiReview) || aiReview[workClass] !== "not-required") continue;
const aiReviewLayer = binding.verification_blocking["ai-review"];
if (!isPlainObject(aiReviewLayer) || aiReviewLayer[workClass] !== "not-required") continue;
const topology = isPlainObject(binding.verification_topology)
? binding.verification_topology[workClass]
: null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ fi
expected_cases="$(node -e 'const m=require(process.argv[1]); console.log(m.cases.join("\n"))' "$MANIFEST")"
for case_name in $expected_cases; do
case "$case_name" in
generate_and_check_clean|drift_fixture|leaf_emission_parity|posture_divergence) ;;
*) fail "manifest names unknown case: $case_name" ;;
generate_and_check_clean | drift_fixture | leaf_emission_parity | posture_divergence) ;;
*) fail "manifest names unknown case: $case_name" ;;
esac
done
ok "manifest_cases: $(echo "$expected_cases" | wc -l | tr -d ' ') named cases exercised"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ function probeUpstreamChangelog(_repoRoot, _surface, machineContext) {
};
}

function probeMergePath(repoRoot, surface, binding) {
function probeMergePath(surface, binding) {
const recorded = binding.surfaces[surface];
if (recorded) {
// A recorded scheduling surface that is not advisory-only can carry merge
Expand Down Expand Up @@ -718,7 +718,7 @@ function runNeedProbe(needId, ctx) {
case "upstream_changelog":
return probeUpstreamChangelog(ctx.repoRoot, ctx.surface, ctx.machineContext);
case "merge_path":
return probeMergePath(ctx.repoRoot, ctx.surface, ctx.binding);
return probeMergePath(ctx.surface, ctx.binding);
default:
return {
result: "unresolvable",
Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-memory/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "claude-memory",
"version": "0.11.14",
"version": "0.11.15",
"description": "Keeps a repo's Claude Code memory layer healthy and under your control, against criteria derived from official Claude Code documentation. The audit skill checks the instruction/memory layer (CLAUDE.md, CLAUDE.local.md, .claude/rules/, auto-memory) with a deterministic script-backed spine plus judgment-tier checks. The stateless skill inspects, disables, and (confirm-gated) purges Claude-written auto memory across all settings scopes.",
"author": {
"name": "Melodic Software",
Expand Down
22 changes: 22 additions & 0 deletions plugins/claude-memory/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@
All notable changes to the `claude-memory` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.11.15]

### Fixed

- **Two audit suites now drop an ambient `CLAUDE_CONFIG_DIR`.**
`memory-index-refs-check.test.sh` and `enumerate-all-projects.test.sh` isolated
`$HOME` but not the variable the memory-dir resolver prefers over it, so a value
inherited from the caller's environment pointed them at the host's real config
root instead of their fixture. With a decoy root exported they fail 7 of 15 and
8 of 19 respectively, and can also be made to pass while every assertion is
answered by the host rather than the fixture. Both now use `env -u`, matching
the fix applied to the sibling `scope-report` suite in 0.11.13.

### Changed

- **Audit and stateless script tidyings from the repo-wide sweep.**
`discover-instruction-surfaces.sh` drops a dead emptiness guard on a value that
cannot be empty; `resolve-memory-dir.sh` runs `git rev-parse` once instead of
twice in its `cygpath` fallback pair, matching the two-step the hub-slug path
below it already uses; four history-narration comments become present-tense
rationale. 181 checks across six suites, identical before and after.

## [0.11.14]

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ fi
# Same resolution as resolve-memory-dir.sh: CLAUDE_CONFIG_DIR relocates the whole
# `~/.claude` tree when set, so the instruction surfaces move with it.

if [[ -n "$config_root" && -d "$config_root" ]]; then
if [[ -d "$config_root" ]]; then
# Suppressed when it is the same physical file as the project one, already emitted
# above as `both`.
if [[ -f "$config_root/CLAUDE.md" && "$md_overlap" -eq 0 ]]; then
Expand Down
Loading
Loading