@cosmicdrift/kumiko-guards 0.362.0 → 0.364.0
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/README.md +28 -0
- package/package.json +2 -2
- package/src/_lib/agent-docs.ts +425 -0
- package/src/_lib/bun-lock-packages.ts +1 -1
- package/src/_lib/dependency-cli.ts +27 -0
- package/src/_lib/dependency-exceptions.ts +135 -0
- package/src/_lib/direct-dependencies.ts +80 -0
- package/src/_lib/guard-kit.ts +7 -2
- package/src/_lib/installed-packages.ts +146 -0
- package/src/_lib/npm-registry.ts +168 -0
- package/src/_lib/security-audit.ts +390 -0
- package/src/changes.json +41 -5
- package/src/check-as-casts.ts +93 -47
- package/src/check-complexity.ts +1 -1
- package/src/check-licenses.ts +288 -0
- package/src/check-outdated.ts +234 -0
- package/src/check-runtime-isolation.ts +3 -1
- package/src/check-security.ts +182 -0
- package/src/check-single-runtime-instance.ts +10 -4
- package/src/cli.ts +27 -10
- package/src/dependency-license-exceptions.json +77 -0
- package/src/dependency-security-exceptions.json +4 -0
- package/src/guard-admin-api.ts +3 -3
- package/src/guard-agent-docs-drift.ts +137 -0
- package/src/guard-comment-lang.ts +76 -40
- package/src/guard-direct-entity-writes.ts +3 -0
- package/src/guard-html-escape.ts +2 -2
- package/src/guard-pre-es-patterns.ts +3 -0
- package/src/guard-public-exports-changelog.ts +22 -9
- package/src/guard-raw-sql.ts +1 -1
- package/src/guard-thin-wrappers.ts +11 -5
- package/src/guard-upgrade-state.ts +55 -31
- package/src/index.ts +1 -0
- package/src/pre-push.sh +19 -0
- package/src/run-repo-checks.ts +2 -0
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* bun guard-upgrade-state.ts
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
24
25
|
import { existsSync, readFileSync } from "node:fs";
|
|
25
26
|
import { join } from "node:path";
|
|
26
27
|
import {
|
|
@@ -60,45 +61,69 @@ export function resolveInstalledVersion(json: UpgradeJson): string {
|
|
|
60
61
|
return json.installedVersion ?? json.currentVersion;
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
65
|
+
return typeof value === "object" && value !== null;
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
export function isPendingEntry(value: unknown): value is PendingEntry {
|
|
64
|
-
if (!value
|
|
65
|
-
const v = value as Record<string, unknown>;
|
|
69
|
+
if (!isRecord(value)) return false;
|
|
66
70
|
return (
|
|
67
|
-
typeof
|
|
68
|
-
typeof
|
|
69
|
-
typeof
|
|
71
|
+
typeof value["version"] === "string" &&
|
|
72
|
+
typeof value["type"] === "string" &&
|
|
73
|
+
typeof value["title"] === "string"
|
|
70
74
|
);
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
export function isUpgradeJson(value: unknown): value is UpgradeJson {
|
|
74
|
-
if (!value
|
|
75
|
-
const
|
|
76
|
-
const installed = v["installedVersion"];
|
|
78
|
+
if (!isRecord(value)) return false;
|
|
79
|
+
const installed = value["installedVersion"];
|
|
77
80
|
return (
|
|
78
|
-
typeof
|
|
81
|
+
typeof value["currentVersion"] === "string" &&
|
|
79
82
|
(installed === undefined || installed === null || typeof installed === "string") &&
|
|
80
|
-
Array.isArray(
|
|
81
|
-
|
|
83
|
+
Array.isArray(value["pending"]) &&
|
|
84
|
+
value["pending"].every(isPendingEntry)
|
|
82
85
|
);
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
typeof v["title"] === "string"
|
|
93
|
-
);
|
|
88
|
+
// Same id scheme as manualEntryId in @cosmicdrift/kumiko-framework/upgrade-cli. Duplicated because
|
|
89
|
+
// this package must stay installable without the framework; a test pins both to the same value.
|
|
90
|
+
const MANUAL_ID_HASH_LENGTH = 8;
|
|
91
|
+
|
|
92
|
+
export function manualEntryId(version: string, title: string): string {
|
|
93
|
+
const hash = createHash("sha256").update(title).digest("hex").slice(0, MANUAL_ID_HASH_LENGTH);
|
|
94
|
+
return `${version}:${hash}`;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return
|
|
97
|
+
/** Markers written before entries carried an `id` get the id the CLI derives for them. */
|
|
98
|
+
export function parsePendingManualStep(value: unknown): PendingManualStep | null {
|
|
99
|
+
if (!isRecord(value)) return null;
|
|
100
|
+
const { id, version, title } = value;
|
|
101
|
+
if (typeof version !== "string" || typeof title !== "string") return null;
|
|
102
|
+
return {
|
|
103
|
+
id: typeof id === "string" && id.length > 0 ? id : manualEntryId(version, title),
|
|
104
|
+
version,
|
|
105
|
+
title,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function readPendingManual(
|
|
110
|
+
parsed: unknown,
|
|
111
|
+
): { readonly steps: readonly PendingManualStep[] } | { readonly error: string } {
|
|
112
|
+
const raw = isRecord(parsed) ? parsed["pendingManual"] : undefined;
|
|
113
|
+
if (raw === undefined) return { steps: [] };
|
|
114
|
+
if (!Array.isArray(raw))
|
|
115
|
+
return { error: `${MARKER_REL} has a "pendingManual" that is not an array` };
|
|
116
|
+
const steps: PendingManualStep[] = [];
|
|
117
|
+
for (const [index, entry] of raw.entries()) {
|
|
118
|
+
const step = parsePendingManualStep(entry);
|
|
119
|
+
if (step === null) {
|
|
120
|
+
return {
|
|
121
|
+
error: `${MARKER_REL} has a malformed pendingManual entry at index ${index} (needs string "version" and "title")`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
steps.push(step);
|
|
125
|
+
}
|
|
126
|
+
return { steps };
|
|
102
127
|
}
|
|
103
128
|
|
|
104
129
|
export function readMarker(root: string): UpgradeMarker | { error: string } {
|
|
@@ -114,16 +139,15 @@ export function readMarker(root: string): UpgradeMarker | { error: string } {
|
|
|
114
139
|
} catch {
|
|
115
140
|
return { error: `${MARKER_REL} is not valid JSON — broken marker file` };
|
|
116
141
|
}
|
|
117
|
-
const version =
|
|
118
|
-
parsed && typeof parsed === "object"
|
|
119
|
-
? (parsed as Record<string, unknown>)["version"]
|
|
120
|
-
: undefined;
|
|
142
|
+
const version = isRecord(parsed) ? parsed["version"] : undefined;
|
|
121
143
|
if (typeof version !== "string" || !SEMVER_RE.test(version)) {
|
|
122
144
|
return {
|
|
123
145
|
error: `${MARKER_REL} is missing a valid "version" field (expected semver x.y.z[-pre][+build]) — broken marker file`,
|
|
124
146
|
};
|
|
125
147
|
}
|
|
126
|
-
|
|
148
|
+
const pendingManual = readPendingManual(parsed);
|
|
149
|
+
if ("error" in pendingManual) return pendingManual;
|
|
150
|
+
return { version, pendingManual: pendingManual.steps };
|
|
127
151
|
}
|
|
128
152
|
|
|
129
153
|
/** Pending changelog entries turned into guard violations — pure, testable without a subprocess. */
|
package/src/index.ts
CHANGED
package/src/pre-push.sh
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
# sub-repo's own package.json `test` script — refuses the push if that
|
|
19
19
|
# script is missing.
|
|
20
20
|
#
|
|
21
|
+
# Before the checks below, the hook runs a gitleaks scan of origin/main..HEAD
|
|
22
|
+
# when the binary is installed.
|
|
23
|
+
#
|
|
21
24
|
# Extension point: a tracked, executable scripts/pre-push-extra.sh runs
|
|
22
25
|
# before the main check in every branch above, with KUMIKO_PUSH_REPO_ROOT
|
|
23
26
|
# and KUMIKO_PUSH_PARENT_DIR available inline (never exported).
|
|
@@ -118,6 +121,22 @@ if [ -f "$REPO_ROOT/.kumiko/upgrade-state.json" ]; then
|
|
|
118
121
|
fi
|
|
119
122
|
fi
|
|
120
123
|
|
|
124
|
+
# Secret scan: runs before check-wt.sh/bun check so a leaked credential is
|
|
125
|
+
# caught in seconds, not after the long checks. A missing binary or a missing
|
|
126
|
+
# origin/main only warns, so a machine without gitleaks is not locked out.
|
|
127
|
+
# No --config: gitleaks reads a .gitleaks.toml in the repo root itself.
|
|
128
|
+
if ! command -v gitleaks >/dev/null 2>&1; then
|
|
129
|
+
echo "[pre-push] gitleaks not installed — secret scan skipped. Install: brew install gitleaks" >&2
|
|
130
|
+
elif ! git -C "$REPO_ROOT" rev-parse --verify --quiet origin/main >/dev/null 2>&1; then
|
|
131
|
+
echo "[pre-push] origin/main not found — secret scan skipped" >&2
|
|
132
|
+
else
|
|
133
|
+
echo "[pre-push] gitleaks git origin/main..HEAD"
|
|
134
|
+
if ! (cd "$REPO_ROOT" && gitleaks git --no-banner --redact --log-opts="origin/main..HEAD" .); then
|
|
135
|
+
echo "[pre-push] gitleaks found secrets — refusing push" >&2
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
121
140
|
# infra#559/#569: from a worktree, `--git-common-dir` resolves to the main
|
|
122
141
|
# checkout's .git, so the branch below would scope `bun check` to the
|
|
123
142
|
# canonical sibling path instead of this worktree — checking the wrong
|
package/src/run-repo-checks.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { check as realProviderIsolation } from "./check-real-provider-isolation"
|
|
|
7
7
|
import { check as runtimeIsolation } from "./check-runtime-isolation";
|
|
8
8
|
import { check as secretLiterals } from "./check-secret-literals";
|
|
9
9
|
import { check as singleRuntimeInstance } from "./check-single-runtime-instance";
|
|
10
|
+
import { check as agentDocsDrift } from "./guard-agent-docs-drift";
|
|
10
11
|
import { check as featureIntegrationTests } from "./guard-feature-integration-tests";
|
|
11
12
|
import { check as noDirectProcessEnv } from "./guard-no-direct-process-env";
|
|
12
13
|
import { check as primitivesDiscipline } from "./guard-primitives-discipline";
|
|
@@ -29,6 +30,7 @@ export const REPO_CHECKS = [
|
|
|
29
30
|
upgradeState,
|
|
30
31
|
realProviderIsolation,
|
|
31
32
|
singleRuntimeInstance,
|
|
33
|
+
agentDocsDrift,
|
|
32
34
|
];
|
|
33
35
|
|
|
34
36
|
export const REPO_CHECK_FLAGS: readonly string[] = ["--write-baseline"];
|