karajan-code 4.6.0 → 4.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -113,14 +113,26 @@ export async function reviewGateCommand({ config, logger = null, flags = {} }) {
|
|
|
113
113
|
// single test change warn (block via method_gates.tests_with_code).
|
|
114
114
|
const changedFiles = (await rawDiff(flags.range, ["--name-only"])).split("\n").map((f) => f.trim()).filter(Boolean);
|
|
115
115
|
|
|
116
|
-
// KJC-TSK-0688 (MG-C): oversized-diff
|
|
117
|
-
//
|
|
116
|
+
// KJC-TSK-0688 (MG-C) + KJC-TSK-0691 (MG-E): oversized-diff policy.
|
|
117
|
+
// Field case: a 522-line PR sailed past the warning with a
|
|
118
|
+
// rationalization — where the project wants teeth, pr_size: "block"
|
|
119
|
+
// rejects deterministically with a NAMED escape. Default stays warn.
|
|
118
120
|
const sizeWarn = config?.method_gates?.pr_size_warn ?? 150;
|
|
119
121
|
if (sizeWarn > 0) {
|
|
120
122
|
const numstat = await rawDiff(flags.range, ["--numstat"]);
|
|
121
123
|
const added = numstat.split("\n").reduce((acc, l) => acc + (Number(l.split("\t")[0]) || 0), 0);
|
|
122
124
|
if (added > sizeWarn) {
|
|
123
|
-
|
|
125
|
+
const sizePolicy = config?.method_gates?.pr_size || "warn";
|
|
126
|
+
if (process.env.KJ_ALLOW_LARGE_PR === "1") {
|
|
127
|
+
console.log(`⚠ pr-size exempt: ${added} lines added — KJ_ALLOW_LARGE_PR=1 (explicit escape hatch)`);
|
|
128
|
+
} else if (sizePolicy === "block") {
|
|
129
|
+
const reason = `${added} lines added exceeds the ${sizeWarn}-line budget (method_gates.pr_size: block) — partition the work, or get your user's explicit OK and re-run with KJ_ALLOW_LARGE_PR=1`;
|
|
130
|
+
console.log(`✗ pr-size gate: ${reason}`);
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
return { verdict: "rejected", reviewer: "pr-size", issues: [{ severity: "high", description: reason }] };
|
|
133
|
+
} else {
|
|
134
|
+
console.log(`⚠ pr-size: ${added} lines added (guideline ~${sizeWarn}) — an oversized warning is not an opinion: partition, or ask your user (method_gates.pr_size: block to harden)`);
|
|
135
|
+
}
|
|
124
136
|
}
|
|
125
137
|
}
|
|
126
138
|
const tests = checkTestsWithCode({ config, stagedFiles: changedFiles });
|
|
@@ -66,6 +66,10 @@ Invariants (the git gates enforce these — they are not suggestions):
|
|
|
66
66
|
and it must be reviewed again. Disagree with a rejection? \`kj solomon\`.
|
|
67
67
|
- Security findings are never overridable — not even by arbitration. You
|
|
68
68
|
absorb the security role: \`kj brief security\` states what must be true.
|
|
69
|
+
- A design decision the card's AC don't cover belongs to the USER: record
|
|
70
|
+
it as a proposed ADR (\`kj adr add\`) and ask — never bury it in a PR
|
|
71
|
+
bullet. An oversized-diff warning is not an opinion either: partition,
|
|
72
|
+
or get an explicit OK.
|
|
69
73
|
- Branch first: never commit on the base branch — every change reaches it
|
|
70
74
|
through an atomic PR (~150 net lines, Conventional Commits). More than one
|
|
71
75
|
task, or the base tree must stay untouched? \`kj worktree start <slug>\`
|
|
@@ -81,6 +85,8 @@ architect, tester, security, audit, board) · \`kj hu add|move|list\` ·
|
|
|
81
85
|
|
|
82
86
|
Hit a kj bug or friction? Diagnose it and file it upstream with
|
|
83
87
|
\`kj report-issue\` (sanitized; ask your user before \`--publish\`).
|
|
88
|
+
kj announces a newer version? Tell your user what it brings and ask —
|
|
89
|
+
never run \`kj update\` on your own.
|
|
84
90
|
`;
|
|
85
91
|
|
|
86
92
|
export function renderPlaybook({ stateBackend = "hu-board", boardName = null } = {}) {
|
|
@@ -16,8 +16,8 @@ export const SHEBANG = "#!/usr/bin/env sh";
|
|
|
16
16
|
/** Hooks enabled per profile. */
|
|
17
17
|
export const PROFILE_HOOKS = {
|
|
18
18
|
minimal: ["commit-msg"],
|
|
19
|
-
standard: ["pre-commit", "commit-msg", "pre-push", "post-merge"],
|
|
20
|
-
strict: ["pre-commit", "commit-msg", "pre-push", "post-merge"],
|
|
19
|
+
standard: ["pre-commit", "prepare-commit-msg", "commit-msg", "pre-push", "post-merge"],
|
|
20
|
+
strict: ["pre-commit", "prepare-commit-msg", "commit-msg", "pre-push", "post-merge"],
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
/** Build the managed body for a single hook. */
|
|
@@ -99,6 +99,37 @@ export function hookBody(hook, cmds = {}, { globalHooksDir = null, baseBranch =
|
|
|
99
99
|
else lines.push("# (no test command detected for this stack)");
|
|
100
100
|
return [...lines, ...chain].join("\n");
|
|
101
101
|
}
|
|
102
|
+
case "prepare-commit-msg":
|
|
103
|
+
// KJC-TSK-0692 (MG-F): the branch already carries the card ref
|
|
104
|
+
// (validated by the card-first gate) — the subject inherits it
|
|
105
|
+
// automatically. A convention that enforces itself needs no adoption.
|
|
106
|
+
return [
|
|
107
|
+
'msg_file="$1"',
|
|
108
|
+
'branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")',
|
|
109
|
+
"# POSIX-safe extraction (no grep -o): the ref is the leading",
|
|
110
|
+
"# card-shaped token of the branch's last segment (feat/REF-desc).",
|
|
111
|
+
"seg=$(printf '%s' \"$branch\" | tr '[:upper:]' '[:lower:]'); seg=\"${seg##*/}\"",
|
|
112
|
+
"ref=$(printf '%s' \"$seg\" | sed -nE 's/^([a-z][a-z0-9]{1,9}(-[a-z][a-z0-9]{1,9})*-[0-9]{1,6})(-.*)?$/\\1/p')",
|
|
113
|
+
'header=$(head -n1 "$msg_file")',
|
|
114
|
+
'if [ -n "$ref" ] && [ -n "$header" ]; then',
|
|
115
|
+
' case "$header" in',
|
|
116
|
+
" Merge*|fixup!*|squash!*) ;;",
|
|
117
|
+
" *)",
|
|
118
|
+
" # Boundary match — a header mentioning KJW-TSK-00011 must not",
|
|
119
|
+
" # suppress the KJW-TSK-0001 stamp. $ref is [a-z0-9-] only by",
|
|
120
|
+
" # construction (sed extraction above), safe inside the ERE.",
|
|
121
|
+
' if ! printf \'%s\' "$header" | grep -qiE "(^|[^a-zA-Z0-9-])$ref([^a-zA-Z0-9-]|$)"; then',
|
|
122
|
+
" stamp=$(printf '%s' \"$ref\" | tr '[:lower:]' '[:upper:]')",
|
|
123
|
+
' new_header="$header ($stamp)"',
|
|
124
|
+
' if [ "${#new_header}" -le 100 ]; then',
|
|
125
|
+
" { printf '%s\\n' \"$new_header\"; tail -n +2 \"$msg_file\"; } > \"$msg_file.kjtmp\" && mv \"$msg_file.kjtmp\" \"$msg_file\"",
|
|
126
|
+
" fi",
|
|
127
|
+
" fi",
|
|
128
|
+
" ;;",
|
|
129
|
+
" esac",
|
|
130
|
+
"fi",
|
|
131
|
+
...chain,
|
|
132
|
+
].join("\n");
|
|
102
133
|
case "post-merge":
|
|
103
134
|
return [
|
|
104
135
|
"# Refresh the local RAG index so retrieval never serves stale code.",
|
|
@@ -5,8 +5,38 @@ import { isSea } from "node:sea";
|
|
|
5
5
|
import { getKarajanHome } from "./paths.js";
|
|
6
6
|
|
|
7
7
|
const CACHE_FILE = "update-check.json";
|
|
8
|
-
|
|
8
|
+
// KJC-TSK-0690: 6h — kj can ship several releases a day; a session should
|
|
9
|
+
// not work a full day blind. KJ_NO_UPDATE_CHECK=1 opts out (CI).
|
|
10
|
+
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
9
11
|
const PACKAGE_NAME = "karajan-code";
|
|
12
|
+
const CHANGELOG_URL = "https://raw.githubusercontent.com/manufosela/karajan-code/main/CHANGELOG.md";
|
|
13
|
+
const HIGHLIGHT_MAX = 160;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* First paragraph of a version's CHANGELOG section — the release headline
|
|
17
|
+
* ("Minor. **The method, enforced.** …"), bold stripped, truncated. Pure.
|
|
18
|
+
*/
|
|
19
|
+
export function parseChangelogHighlight(markdown, version) {
|
|
20
|
+
const section = String(markdown).split(new RegExp(`^## \\[${version.replaceAll(".", "\\.")}\\][^\\n]*$`, "m"))[1];
|
|
21
|
+
if (!section) return null;
|
|
22
|
+
const firstLine = section.split(/^##|^###/m)[0].split("\n").map((l) => l.trim()).find(Boolean);
|
|
23
|
+
if (!firstLine) return null;
|
|
24
|
+
const clean = firstLine.replaceAll("**", "");
|
|
25
|
+
return clean.length > HIGHLIGHT_MAX ? `${clean.slice(0, HIGHLIGHT_MAX - 1)}…` : clean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function fetchHighlight(version) {
|
|
29
|
+
try {
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
32
|
+
const res = await fetch(CHANGELOG_URL, { signal: controller.signal });
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
if (!res.ok) return null;
|
|
35
|
+
return parseChangelogHighlight(await res.text(), version);
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
10
40
|
|
|
11
41
|
const RAW_BASE = "https://raw.githubusercontent.com/manufosela/karajan-code/main/scripts";
|
|
12
42
|
export const INSTALL_SH_URL = `${RAW_BASE}/install-binary.sh`;
|
|
@@ -48,6 +78,7 @@ export function detectInstallChannel() {
|
|
|
48
78
|
* Returns { updateAvailable, latest, current } or null if check fails/cached.
|
|
49
79
|
*/
|
|
50
80
|
export async function checkForUpdate(currentVersion) {
|
|
81
|
+
if (process.env.KJ_NO_UPDATE_CHECK === "1") return null;
|
|
51
82
|
try {
|
|
52
83
|
const cachePath = path.join(getKarajanHome(), CACHE_FILE);
|
|
53
84
|
|
|
@@ -59,7 +90,7 @@ export async function checkForUpdate(currentVersion) {
|
|
|
59
90
|
if (!cache.latest || cache.latest === currentVersion) return null;
|
|
60
91
|
// Only show update if latest is actually NEWER than current
|
|
61
92
|
if (compareVersions(cache.latest, currentVersion) > 0) {
|
|
62
|
-
return { updateAvailable: true, latest: cache.latest, current: currentVersion };
|
|
93
|
+
return { updateAvailable: true, latest: cache.latest, current: currentVersion, highlight: cache.highlight || null };
|
|
63
94
|
}
|
|
64
95
|
return null;
|
|
65
96
|
}
|
|
@@ -78,14 +109,16 @@ export async function checkForUpdate(currentVersion) {
|
|
|
78
109
|
const data = await res.json();
|
|
79
110
|
const latest = data.version;
|
|
80
111
|
|
|
112
|
+
// KJC-TSK-0690: a NEWER version also fetches its headline once — the
|
|
113
|
+
// notice tells the user what it brings, not just that it exists.
|
|
114
|
+
const newer = latest !== currentVersion && compareVersions(latest, currentVersion) > 0;
|
|
115
|
+
const highlight = newer ? await fetchHighlight(latest) : null;
|
|
116
|
+
|
|
81
117
|
// Save cache
|
|
82
118
|
await fs.mkdir(getKarajanHome(), { recursive: true });
|
|
83
|
-
await fs.writeFile(cachePath, JSON.stringify({ latest, checkedAt: Date.now() }), "utf8");
|
|
119
|
+
await fs.writeFile(cachePath, JSON.stringify({ latest, checkedAt: Date.now(), highlight }), "utf8");
|
|
84
120
|
|
|
85
|
-
if (latest
|
|
86
|
-
if (compareVersions(latest, currentVersion) > 0) {
|
|
87
|
-
return { updateAvailable: true, latest, current: currentVersion };
|
|
88
|
-
}
|
|
121
|
+
if (newer) return { updateAvailable: true, latest, current: currentVersion, highlight };
|
|
89
122
|
return null;
|
|
90
123
|
} catch {
|
|
91
124
|
return null; // Network error, offline, etc. Never block.
|
|
@@ -100,7 +133,9 @@ export async function printUpdateNotice(currentVersion) {
|
|
|
100
133
|
if (result?.updateAvailable) {
|
|
101
134
|
const channel = detectInstallChannel();
|
|
102
135
|
console.log(`\n Update available: v${result.current} → v${result.latest}`);
|
|
103
|
-
console.log(` ${
|
|
136
|
+
if (result.highlight) console.log(` What it brings: ${result.highlight}`);
|
|
137
|
+
console.log(` ${updateInstruction({ channel })}`);
|
|
138
|
+
console.log(" Agent: tell your user and ask before updating — never update on your own.\n");
|
|
104
139
|
}
|
|
105
140
|
}
|
|
106
141
|
|