From 51ad5c9072dea3fe9b63025e5ccb1cf9d93f0ce6 Mon Sep 17 00:00:00 2001
From: MongLong0214
Date: Tue, 8 Sep 2026 16:03:57 +0900
Subject: [PATCH 1/2] fix(doctor): attribute a preserved hook's failure to the
preserved hook
`hook-runtime` ran the installed stub as one process and read its exit as the
stub's. But the stub runs the hook it preserved at install time first and exits
with that hook's code, verbatim, before commitlore is resolved. So a preserved
hook calling `node` by name died with 127 under git's PATH, and the row said
"the hook cannot find a node interpreter" about commitlore's hook, with
`commitlore hooks install` as the fix. That command rewrites only commitlore's
own file, reported it unchanged, and the next `doctor` failed identically (#876).
The preserved hook now runs on its own first, under the same PATH-less
environment and through sh the way the stub invokes it. If it exits non-zero the
row says commitlore's hook is not what failed, names the preserved hook by path,
classifies its first stderr line the same way the stub's was (node missing, node
threw, unclear), and prescribes a fix aimed at that file. Only when it has passed
does the stub run, so every remaining failure is commitlore's own resolution.
`commit-msg-hook` inherits the runtime row's outcome when it is blocked on it; it
now inherits the fix as well. It was the row the reporter read, and it kept
saying `hooks install` under an outcome that had just explained why that could
not help.
Not addressed: the stub still lets a broken preserved hook block every commit.
That is the chaining contract (`|| exit $?`) and a hook that was rejecting
commits before commitlore arrived must keep rejecting them; whether a hook that
is broken rather than rejecting deserves different treatment is a separate
decision, not a diagnostic one.
Verified with a harness against the compiled check because vitest's native
binding cannot load in the sandbox this was written in: the reporter's shape
(preserved hook `exec node ...` -> 127) names the preserved hook on both rows
with no `hooks install` in either fix; a preserved hook exiting 3 with unrelated
stderr does the same without claiming node is missing; a passing preserved hook
followed by a missing `commitlore.node` still blames commitlore and prescribes
`hooks install`; a preserved hook without its execute bit is ignored as the
stub's `[ -x ]` ignores it; a preserved hook with no shebang still runs. The same
cases are in test/doctor.test.ts for CI.
Closes #876
Record-Id: r-doctorchainedhook
Provenance: authored
Ruled-out: teaching the stub to print a marker line before exec-ing the preserved hook so one run could be split | the stub is byte-compared by `hooks status` and doctor, so changing it flags every existing install as outdated for a diagnostic-only gain
Ruled-out: parsing the stub's stderr for the preserved hook's path | the preserved hook's own error text is the only content, and a hook that fails silently leaves nothing to parse
Limit: the preserved hook runs twice per doctor run (alone, then inside the stub); commit-msg hooks are expected to be re-runnable on a message file
Warn: evidence paths are normalised, so tests must pin the chained hook's name, not its absolute path
Blast: module
Undo: easy
Certainty: firm
Co-Authored-By: Claude
---
.../doctor/checks/capture-commit-msg-hook.ts | 9 +-
.../doctor/checks/capture-hook-runtime.ts | 108 ++++++++++++++++--
test/doctor.test.ts | 92 ++++++++++++++-
3 files changed, 197 insertions(+), 12 deletions(-)
diff --git a/src/commands/doctor/checks/capture-commit-msg-hook.ts b/src/commands/doctor/checks/capture-commit-msg-hook.ts
index dc7471f9..73f3cbf1 100644
--- a/src/commands/doctor/checks/capture-commit-msg-hook.ts
+++ b/src/commands/doctor/checks/capture-commit-msg-hook.ts
@@ -115,6 +115,11 @@ export const checkHook = (ctx: DoctorContext, runtime?: DoctorCheck): DoctorChec
];
if (runtime !== undefined && runtime.status !== 'ok') {
const inherited = `installed at ${path}; ${targetDetail}; outcome: ${runtime.detail}`;
+ // This row is blocked on the runtime's finding, so the fix that moves that
+ // finding is the only fix that moves this one. Prescribing `hooks install`
+ // here regardless is how a preserved hook's failure came to carry a remedy
+ // that reinstalls the hook which worked (#876).
+ const inheritedFix = runtime.fix ?? install;
// A skipped runtime would make this row a skip too, and a skip has to name
// a reason. Inheriting the runtime's is the only answer that stays true —
// this row did not look for the same reason that one did not. The branch is
@@ -130,7 +135,7 @@ export const checkHook = (ctx: DoctorContext, runtime?: DoctorCheck): DoctorChec
title,
'skipped',
inherited,
- install,
+ inheritedFix,
false,
false,
{
@@ -148,7 +153,7 @@ export const checkHook = (ctx: DoctorContext, runtime?: DoctorCheck): DoctorChec
title,
runtime.status,
inherited,
- install,
+ inheritedFix,
false,
undefined,
{ evidence: { ...hookEvidence, runtime_status: runtime.status } },
diff --git a/src/commands/doctor/checks/capture-hook-runtime.ts b/src/commands/doctor/checks/capture-hook-runtime.ts
index 9d2b018b..b7f554a2 100644
--- a/src/commands/doctor/checks/capture-hook-runtime.ts
+++ b/src/commands/doctor/checks/capture-hook-runtime.ts
@@ -5,12 +5,39 @@
* receive its completed row through the registry rather than importing it.
*/
-import { existsSync, rmSync, writeFileSync } from 'node:fs';
+import { accessSync, constants as fsConstants, existsSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir as tmpdirPath } from 'node:os';
-import { join, resolve } from 'node:path';
+import { dirname, join, resolve } from 'node:path';
+import { CHAINED_HOOK_NAME } from '../../../hooks/commit-msg.js';
import { check, gitOptions, PROBE_MESSAGE, streamEvidence, type Category, type DoctorCheck, type DoctorContext } from '../model.js';
+/**
+ * The stub runs `"$chained" "$@"` only when `[ -x "$chained" ]` holds, so a
+ * preserved hook without its execute bit is inert to git and to the stub alike.
+ * The same test here, so this check does not probe a file the hook will skip.
+ */
+const isExecutable = (path: string): boolean => {
+ try {
+ accessSync(path, fsConstants.X_OK);
+ return true;
+ } catch {
+ return false;
+ }
+};
+
+/**
+ * How the stub's failure reads on its first stderr line: node was never found,
+ * node ran and threw, or neither. Shared between the two hooks this check runs,
+ * because the preserved hook fails in the same three shapes and the
+ * classification is about the line, not about who wrote it.
+ */
+const classifyFailure = (status: number | null, said: string): 'node-missing' | 'node-threw' | 'unclear' => {
+ if (status === 127 || /\bnode\b.*not found|ENOENT|command not found.*\bnode\b/i.test(said)) return 'node-missing';
+ if (/^\s*at\s|\.js:\d+/.test(said)) return 'node-threw';
+ return 'unclear';
+};
+
/**
* Whether the installed hook actually runs, in the environment git gives it.
*
@@ -29,6 +56,12 @@ import { check, gitOptions, PROBE_MESSAGE, streamEvidence, type Category, type D
* The probe message is valid, so a healthy hook exits 0. A hook that cannot find
* a runtime exits non-zero having parsed nothing, which is indistinguishable
* from "your message was fine" to everyone except this check.
+ *
+ * Two hooks run here, not one. The stub hands the message to the hook it
+ * preserved at install time before it resolves commitlore, and exits with that
+ * hook's code if it fails -- so the preserved hook is probed on its own first,
+ * and its failure is reported as its own, with a fix aimed at it. `hooks
+ * install` cannot move a finding about a file it does not write (#876).
*/
export const checkHookRuntime = (ctx: DoctorContext): DoctorCheck => {
const { opts, git, spawn, env } = ctx;
@@ -77,15 +110,71 @@ export const checkHookRuntime = (ctx: DoctorContext): DoctorCheck => {
}
const probe = join(tmpdirPath(), `commitlore-doctor-${String(process.pid)}.txt`);
+ // No node, and no PATH entry that could supply one. `git` must stay
+ // reachable: the hook reads its own config through it.
+ const hookEnv = { PATH: '/usr/bin:/bin', HOME: env['HOME'] ?? '' };
try {
+ // The stub runs the hook it preserved at install time first, and that
+ // hook's non-zero exit is the stub's exit, verbatim, before commitlore is
+ // reached. Probed through the stub alone, the two are one process with one
+ // stderr, and the row attributed a preserved hook's `node: command not
+ // found` to the installed hook and prescribed `hooks install` -- which
+ // reports the file unchanged, because the file it writes was never the one
+ // failing (#876). So the preserved hook runs on its own first, the way the
+ // stub runs it: through sh, so a script without a shebang behaves the same
+ // here as it does there.
+ const chained = join(dirname(hook), CHAINED_HOOK_NAME);
+ if (isExecutable(chained)) {
+ writeFileSync(probe, PROBE_MESSAGE);
+ const preserved = spawn('/bin/sh', ['-c', '"$0" "$1"', chained, probe], {
+ shell: false,
+ encoding: 'utf8',
+ cwd,
+ env: hookEnv,
+ });
+ const exit = preserved.error === undefined ? preserved.status : null;
+ if (preserved.error !== undefined || exit !== 0) {
+ const spoke = `${preserved.stderr ?? ''}`.trim();
+ const said = preserved.error?.message ?? (spoke.split('\n')[0] ?? '');
+ const shape = preserved.error === undefined ? classifyFailure(exit, said) : 'unclear';
+ const because =
+ shape === 'node-missing'
+ ? `it calls node by name and git's PATH has none: ${said}`
+ : shape === 'node-threw'
+ ? `its node process ran but threw (exit ${String(exit)}): ${said}`
+ : `it exited ${String(exit ?? 'unavailable')} under the restricted PATH: ${said || 'no output'}`;
+ return check(
+ id,
+ category,
+ title,
+ 'fail',
+ `commitlore's hook is not what failed. It runs the hook it preserved first, and that hook -- ${chained} -- stops the commit before commitlore is reached: ${because}. That file was this repository's commit-msg hook before commitlore was installed; \`hooks install\` rewrites only commitlore's own and leaves it as it is`,
+ shape === 'node-missing'
+ ? `edit ${chained} to call node by absolute path (or remove it if it is no longer wanted)`
+ : `fix or remove ${chained}`,
+ false,
+ undefined,
+ {
+ evidence: {
+ hook_path: hook,
+ chained_hook_path: chained,
+ exit_code: String(exit ?? 'unavailable'),
+ ...(preserved.error === undefined ? {} : { error: preserved.error.message }),
+ ...streamEvidence('stderr', preserved.stderr ?? ''),
+ },
+ },
+ );
+ }
+ }
+
+ // A commit-msg hook may rewrite the message it is given; the probe is
+ // written again so the stub reads the same bytes the preserved hook did.
writeFileSync(probe, PROBE_MESSAGE);
const run = spawn('/bin/sh', [hook, probe], {
shell: false,
encoding: 'utf8',
cwd,
- // No node, and no PATH entry that could supply one. `git` must stay
- // reachable: the hook reads its own config through it.
- env: { PATH: '/usr/bin:/bin', HOME: env['HOME'] ?? '' },
+ env: hookEnv,
});
if (run.error !== undefined) {
@@ -111,10 +200,11 @@ export const checkHookRuntime = (ctx: DoctorContext): DoctorCheck => {
if (run.status !== 0) {
const spoke = `${run.stderr ?? ''}`.trim();
const said = spoke.split('\n')[0] ?? '';
- const nodeMissing =
- run.status === 127 ||
- /\bnode\b.*not found|ENOENT|command not found.*\bnode\b/i.test(said);
- const nodeThrew = /^\s*at\s|\.js:\d+/.test(said);
+ // The preserved hook, if any, has already exited 0 on its own above, so
+ // whatever follows is commitlore's resolution failing, not a hand-off.
+ const shape = classifyFailure(run.status, said);
+ const nodeMissing = shape === 'node-missing';
+ const nodeThrew = shape === 'node-threw';
// The stub says this when the recorded pair resolved and the containment
// check refused it: present, executable, and under a tree this install
// did not record. An upgrade produces it, because `commitlore.bin` follows
diff --git a/test/doctor.test.ts b/test/doctor.test.ts
index ad80e0e2..c66f8488 100644
--- a/test/doctor.test.ts
+++ b/test/doctor.test.ts
@@ -42,7 +42,7 @@ import { POLICY_FILE_NAME } from '../src/core/capture-policy.js';
import { REQUIRE_SIGNED_DIRECTIVE_KEY } from '../src/core/trusted-authors.js';
// The real stub T-202 installs — doctor must recognize that exact file, so the
// fixture is the installer's own output rather than a lookalike.
-import { HOOK_MARKER, commitMsgStub } from '../src/hooks/commit-msg.js';
+import { CHAINED_HOOK_NAME, HOOK_MARKER, commitMsgStub } from '../src/hooks/commit-msg.js';
import {
CLAUDE_HOOK_MARKER,
claudeSettingsPath,
@@ -655,6 +655,96 @@ describe('doctor: hook runtime', () => {
expect(runtime?.detail).toMatch(/unclear|cannot determine/i);
expect(runtime?.detail).not.toContain('carries no node');
});
+
+ /**
+ * #876. The stub runs the hook it preserved at install time first and exits
+ * with that hook's code, so a preserved hook that calls `node` by name dies
+ * with 127 before commitlore is reached. Probed as one process, the row read
+ * that as commitlore's hook failing and prescribed `hooks install`, which
+ * reported the file unchanged and left the failure exactly where it was. The
+ * row has to name the file that produced the exit and offer a fix that can
+ * move it.
+ */
+ const chainedPath = (repo: string): string => join(dirname(hookPath(repo)), CHAINED_HOOK_NAME);
+
+ it('names the preserved hook, not the installed one, when the preserved hook cannot find node', () => {
+ const repo = initRepo('doctor-runtime-chained-no-node');
+ installedHook(repo);
+ // The shape of the reporter's hook: a repository's own commit-msg calling
+ // node by name, which is fine on an interactive PATH and 127 on git's.
+ writeScript(chainedPath(repo), '#!/bin/sh\nexec node /nonexistent/lint-commit.js "$@"\n');
+ chmodSync(chainedPath(repo), 0o755);
+
+ const report = runDoctor({ cwd: repo });
+ const runtime = report.checks.find((entry) => entry.id === 'hook-runtime');
+ expect(runtime?.status).toBe('fail');
+ expect(runtime?.detail).toContain(chainedPath(repo));
+ expect(runtime?.detail).toMatch(/commitlore's hook is not what failed/);
+ expect(runtime?.detail).toContain('node');
+ expect(runtime?.fix).toContain(chainedPath(repo));
+ expect(runtime?.fix).not.toContain('hooks install');
+ // The row the reporter read was `commit-msg-hook`, which inherits the
+ // runtime's outcome. It has to inherit the remedy too, or it keeps saying
+ // `hooks install` under an outcome that just explained why that cannot help.
+ const installation = report.checks.find((entry) => entry.id === 'commit-msg-hook');
+ expect(installation?.status).toBe('fail');
+ expect(installation?.blockedBy).toBe('hook-runtime');
+ expect(installation?.fix).toContain(chainedPath(repo));
+ expect(installation?.fix).not.toContain('hooks install');
+ // Evidence paths are normalised (a home prefix becomes `~`), so the name
+ // is what is pinned, not the absolute string.
+ expect(runtime?.evidence['chained_hook_path']?.endsWith(`/${CHAINED_HOOK_NAME}`)).toBe(true);
+ expect(runtime?.evidence['exit_code']).toBe('127');
+ });
+
+ it('names the preserved hook when it exits non-zero for a reason unrelated to node', () => {
+ const repo = initRepo('doctor-runtime-chained-broken');
+ installedHook(repo);
+ writeScript(chainedPath(repo), '#!/bin/sh\necho "lint config missing" >&2\nexit 3\n');
+ chmodSync(chainedPath(repo), 0o755);
+
+ const runtime = runtimeCheck(repo);
+ expect(runtime?.status).toBe('fail');
+ expect(runtime?.detail).toContain(chainedPath(repo));
+ expect(runtime?.detail).toContain('lint config missing');
+ expect(runtime?.detail).not.toMatch(/the hook cannot find a node interpreter/);
+ expect(runtime?.fix).toContain(chainedPath(repo));
+ expect(runtime?.fix).not.toContain('hooks install');
+ expect(runtime?.evidence['exit_code']).toBe('3');
+ });
+
+ it('still runs the installed hook, and blames it, once the preserved hook has passed', () => {
+ const repo = initRepo('doctor-runtime-chained-ok-then-node-gone');
+ installedHook(repo);
+ writeScript(chainedPath(repo), '#!/bin/sh\nexit 0\n');
+ chmodSync(chainedPath(repo), 0o755);
+ git(repo, ['config', '--local', 'commitlore.node', '/nonexistent/node']);
+
+ const runtime = runtimeCheck(repo);
+ expect(runtime?.status).toBe('fail');
+ expect(runtime?.detail).not.toMatch(/commitlore's hook is not what failed/);
+ expect(runtime?.fix).toContain('hooks install');
+ expect(runtime?.evidence['chained_hook_path']).toBeUndefined();
+ });
+
+ it('reports ok when the preserved hook passes and so does the installed one', () => {
+ const repo = initRepo('doctor-runtime-chained-ok');
+ installedHook(repo);
+ writeScript(chainedPath(repo), '#!/bin/sh\nexit 0\n');
+ chmodSync(chainedPath(repo), 0o755);
+
+ expect(runtimeCheck(repo)?.status).toBe('ok');
+ });
+
+ it('ignores a preserved hook without its execute bit, as the stub does', () => {
+ // `[ -x "$chained" ]` in the stub: git would not have run this file either.
+ const repo = initRepo('doctor-runtime-chained-inert');
+ installedHook(repo);
+ writeScript(chainedPath(repo), '#!/bin/sh\nexit 1\n');
+ chmodSync(chainedPath(repo), 0o644);
+
+ expect(runtimeCheck(repo)?.status).toBe('ok');
+ });
});
describe('doctor: PreToolUse hook runtime', () => {
From 2cd59905b0824309fee3dee77c33e8903a0c26e0 Mon Sep 17 00:00:00 2001
From: MongLong0214
Date: Tue, 8 Sep 2026 16:05:07 +0900
Subject: [PATCH 2/2] release: 1.2.4
Version fields, install pins and the changelog entry for 1.2.4.
The release carries one fix: `doctor` attributed a preserved hook's failure to
commitlore's own hook and prescribed `hooks install`, a remedy that rewrites
only the file that was working (#876). The preserved hook is now probed on its
own first and named by path when it is the one that fails, and the row the
reporter read inherits that fix along with the outcome.
The install pins move; the field-report paragraph in each README keeps saying
v1.2.1, because that is the version the run it describes was made on. The
`v1.2.3` mentioned in the installers' own error text is an example tag, not a
pin, and stays.
`dist/` is deliberately not in this branch. `canonical-merge.yml` rebuilds the
bundle from the merged tree and refuses a pull request that touches it, so the
committed bundle matches the source it lands with.
Co-Authored-By: Claude
---
.claude-plugin/plugin.json | 2 +-
.codex-plugin/plugin.json | 2 +-
CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++
README.ja.md | 12 ++++++------
README.ko.md | 12 ++++++------
README.md | 12 ++++++------
README.zh-CN.md | 12 ++++++------
install.ps1 | 4 ++--
install.sh | 4 ++--
package-lock.json | 4 ++--
package.json | 2 +-
server.json | 6 +++---
12 files changed, 69 insertions(+), 36 deletions(-)
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index 018f0384..b6be6fb4 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "commitlore",
"displayName": "CommitLore",
- "version": "1.2.3",
+ "version": "1.2.4",
"description": "Recorded decisions from git history, delivered to the agent before it edits. Constraints, alternatives already ruled out, and warnings left by whoever was here last.",
"author": {
"name": "MongLong0214",
diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json
index 15cc39b1..da184b0d 100644
--- a/.codex-plugin/plugin.json
+++ b/.codex-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "commitlore",
- "version": "1.2.3",
+ "version": "1.2.4",
"description": "Decision memory from Git history, with verified capture for coding sessions.",
"author": {
"name": "MongLong0214",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8150c2b6..2bf2c752 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,39 @@ Release notes for 1.0.0, 1.0.1 and 1.0.2 are on the
[GitHub releases page](https://github.com/MongLong0214/commitlore/releases); they
were not written here.
+## 1.2.4
+
+`doctor` blamed the hook that worked, and prescribed reinstalling it.
+
+**`hook-runtime` attributed a preserved hook's failure to commitlore's hook
+(#876).** The installed stub runs the hook it preserved at install time first and
+exits with that hook's code, verbatim, before commitlore is resolved. Probed as
+one process, a preserved hook that called `node` by name died with 127 under
+git's PATH and the row read "the hook cannot find a node interpreter", with
+`commitlore hooks install` as the fix. That command rewrites only commitlore's
+own file; it reported the file unchanged, and the next `doctor` failed
+identically. The reporter spent several minutes repairing the wrong component,
+because the diagnostic named it.
+
+**The preserved hook now runs on its own first**, under the same PATH-less
+environment and through `sh` the way the stub invokes it. If it exits non-zero,
+the row says commitlore's hook is not what failed, names the preserved hook by
+path, classifies its first stderr line the same way the stub's was (node
+missing, node threw, unclear), and prescribes a fix aimed at that file. Only once
+it has passed does the stub run, so every remaining failure is commitlore's own
+resolution and `hooks install` is once again a remedy that can move it.
+
+**`commit-msg-hook` inherits the fix along with the outcome.** It was the row the
+reporter read: it already carried the runtime row's outcome when blocked on it,
+and kept saying `hooks install` under an outcome that had just explained why
+that could not help.
+
+Not changed: a broken preserved hook still blocks every commit. That is the
+chaining contract — a hook that was rejecting commits before commitlore arrived
+must keep rejecting them — and whether a hook that is broken rather than
+rejecting deserves different treatment is a separate decision from a diagnostic
+one.
+
## 1.2.3
The capture prompt was the session, so on a long session there was no prompt.
diff --git a/README.ja.md b/README.ja.md
index 1ccf4a88..2fb227b1 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -47,18 +47,18 @@
```bash
-curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh -s v1.2.3
+curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh -s v1.2.4
```
先にインストーラーを読みたいですか?
```bash
-curl -fsSLO https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh
-sh install.sh v1.2.3
+curl -fsSLO https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh
+sh install.sh v1.2.4
# あるいはスクリプトを使わずに。スクリプトが作るチェックアウトは自分でも作れます。
-git clone --depth 1 --branch v1.2.3 https://github.com/MongLong0214/commitlore
+git clone --depth 1 --branch v1.2.4 https://github.com/MongLong0214/commitlore
node commitlore/dist/commitlore.mjs --version
```
@@ -107,13 +107,13 @@ CommitLore はその判断をコードのそばに残します。
macOS と Linux:
```bash
-curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh -s v1.2.3
+curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh -s v1.2.4
```
Windows:
```powershell
-& ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.ps1))) v1.2.3
+& ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.ps1))) v1.2.4
```
Node.js 22.23.2+ と Git が必要です。スクリプトは何かを書き込む前に両方を確認します。
diff --git a/README.ko.md b/README.ko.md
index c5bb0b9f..a5883f2c 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -47,18 +47,18 @@
```bash
-curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh -s v1.2.3
+curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh -s v1.2.4
```
먼저 설치기를 읽어 보고 싶나요?
```bash
-curl -fsSLO https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh
-sh install.sh v1.2.3
+curl -fsSLO https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh
+sh install.sh v1.2.4
# 또는 스크립트를 건너뜁니다. 스크립트가 만드는 체크아웃은 직접 만들 수 있습니다.
-git clone --depth 1 --branch v1.2.3 https://github.com/MongLong0214/commitlore
+git clone --depth 1 --branch v1.2.4 https://github.com/MongLong0214/commitlore
node commitlore/dist/commitlore.mjs --version
```
@@ -107,13 +107,13 @@ CommitLore는 그 판단을 코드 곁에 보관합니다.
macOS와 Linux:
```bash
-curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh -s v1.2.3
+curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh -s v1.2.4
```
Windows:
```powershell
-& ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.ps1))) v1.2.3
+& ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.ps1))) v1.2.4
```
Node.js 22.23.2+와 Git이 필요합니다. 스크립트는 무엇이든 쓰기 전에 둘을 확인합니다.
diff --git a/README.md b/README.md
index 9c0d0634..365acbd0 100644
--- a/README.md
+++ b/README.md
@@ -48,18 +48,18 @@
```bash
-curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh -s v1.2.3
+curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh -s v1.2.4
```
Prefer to read the installer first?
```bash
-curl -fsSLO https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh
-sh install.sh v1.2.3
+curl -fsSLO https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh
+sh install.sh v1.2.4
# Or skip the script: the checkout it makes is one you can make yourself.
-git clone --depth 1 --branch v1.2.3 https://github.com/MongLong0214/commitlore
+git clone --depth 1 --branch v1.2.4 https://github.com/MongLong0214/commitlore
node commitlore/dist/commitlore.mjs --version
```
@@ -109,13 +109,13 @@ preserve, not for narrating every change.
macOS and Linux:
```bash
-curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh -s v1.2.3
+curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh -s v1.2.4
```
Windows:
```powershell
-& ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.ps1))) v1.2.3
+& ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.ps1))) v1.2.4
```
Requires Node.js 22.23.2+ and Git. The script checks both before it writes anything.
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 337ab275..8790036f 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -47,18 +47,18 @@
```bash
-curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh -s v1.2.3
+curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh -s v1.2.4
```
想先阅读安装器吗?
```bash
-curl -fsSLO https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh
-sh install.sh v1.2.3
+curl -fsSLO https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh
+sh install.sh v1.2.4
# 或者跳过脚本:它创建的检出,你自己也能创建。
-git clone --depth 1 --branch v1.2.3 https://github.com/MongLong0214/commitlore
+git clone --depth 1 --branch v1.2.4 https://github.com/MongLong0214/commitlore
node commitlore/dist/commitlore.mjs --version
```
@@ -105,13 +105,13 @@ CommitLore 把那份判断留在代码旁边。
macOS 和 Linux:
```bash
-curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh -s v1.2.3
+curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh -s v1.2.4
```
Windows:
```powershell
-& ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.ps1))) v1.2.3
+& ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.ps1))) v1.2.4
```
需要 Node.js 22.23.2+ 和 Git。脚本会在写入任何内容前检查两者。
diff --git a/install.ps1 b/install.ps1
index 055f8561..24558bcb 100644
--- a/install.ps1
+++ b/install.ps1
@@ -1,8 +1,8 @@
<#
Installs commitlore from source on Windows, for any agent that is not Claude Code.
- irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.ps1 | iex
- & ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.ps1))) v1.2.3
+ irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.ps1 | iex
+ & ([scriptblock]::Create((irm https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.ps1))) v1.2.4
Claude Code users do not need this script. The repository is itself a plugin
marketplace (ADR-0011), so two /plugin commands register the MCP server, the
diff --git a/install.sh b/install.sh
index f77ed7cb..c69df874 100755
--- a/install.sh
+++ b/install.sh
@@ -1,8 +1,8 @@
#!/bin/sh
# Installs commitlore from source, for any agent that is not Claude Code.
#
-# curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh
-# curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh -s v1.2.3
+# curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh
+# curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh -s v1.2.4
#
# **Claude Code users do not need this script.** The repository is itself a
# plugin marketplace (ADR-0011), so two `/plugin` commands register the MCP
diff --git a/package-lock.json b/package-lock.json
index 282256ab..5730f28a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "commitlore",
- "version": "1.2.3",
+ "version": "1.2.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "commitlore",
- "version": "1.2.3",
+ "version": "1.2.4",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.30.0",
diff --git a/package.json b/package.json
index e3d0c2a1..b1b7b646 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "commitlore",
- "version": "1.2.3",
+ "version": "1.2.4",
"description": "Git-native, lifecycle-aware decision memory for coding agents",
"license": "MIT",
"private": true,
diff --git a/server.json b/server.json
index 503946f5..6e4f9cb6 100644
--- a/server.json
+++ b/server.json
@@ -8,7 +8,7 @@
"source": "github"
},
"websiteUrl": "https://github.com/MongLong0214/commitlore#readme",
- "version": "1.2.3",
+ "version": "1.2.4",
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"registryFit": "Distribution is a tagged git checkout plus a Claude Code plugin marketplace (ADR-0011 registry-free git distribution, ADR-0026 no compiled executables and no uploaded release asset), so no official package type applies and this record relies on websiteUrl plus publisher metadata.",
@@ -18,8 +18,8 @@
"/plugin marketplace add MongLong0214/commitlore",
"/plugin install commitlore@commitlore"
],
- "installer": "curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.3/install.sh | sh -s v1.2.3",
- "release": "https://github.com/MongLong0214/commitlore/releases/tag/v1.2.3"
+ "installer": "curl -fsSL https://raw.githubusercontent.com/MongLong0214/commitlore/v1.2.4/install.sh | sh -s v1.2.4",
+ "release": "https://github.com/MongLong0214/commitlore/releases/tag/v1.2.4"
},
"runtime": {
"transport": "stdio",