@cosmicdrift/kumiko-guards 0.363.0 → 0.365.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.
@@ -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 {
@@ -84,19 +85,45 @@ export function isUpgradeJson(value: unknown): value is UpgradeJson {
84
85
  );
85
86
  }
86
87
 
87
- export function isPendingManualStep(value: unknown): value is PendingManualStep {
88
- if (!isRecord(value)) return false;
89
- return (
90
- typeof value["id"] === "string" &&
91
- value["id"].length > 0 &&
92
- typeof value["version"] === "string" &&
93
- typeof value["title"] === "string"
94
- );
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}`;
95
95
  }
96
96
 
97
- function readPendingManual(parsed: unknown): readonly PendingManualStep[] {
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 } {
98
112
  const raw = isRecord(parsed) ? parsed["pendingManual"] : undefined;
99
- return Array.isArray(raw) ? raw.filter(isPendingManualStep) : [];
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 };
100
127
  }
101
128
 
102
129
  export function readMarker(root: string): UpgradeMarker | { error: string } {
@@ -118,7 +145,9 @@ export function readMarker(root: string): UpgradeMarker | { error: string } {
118
145
  error: `${MARKER_REL} is missing a valid "version" field (expected semver x.y.z[-pre][+build]) — broken marker file`,
119
146
  };
120
147
  }
121
- return { version, pendingManual: readPendingManual(parsed) };
148
+ const pendingManual = readPendingManual(parsed);
149
+ if ("error" in pendingManual) return pendingManual;
150
+ return { version, pendingManual: pendingManual.steps };
122
151
  }
123
152
 
124
153
  /** Pending changelog entries turned into guard violations — pure, testable without a subprocess. */
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { AGENT_DOCS_DIR, renderAgentDocs, writeAgentDocs } from "./_lib/agent-docs";
1
2
  export { gitEnv } from "./_lib/git-env";
2
3
  export {
3
4
  type AstGuard,
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
@@ -176,6 +195,7 @@ if [ "$(git rev-parse --path-format=absolute --git-dir)" != "$GIT_COMMON_DIR" ]
176
195
  run_guard_bin guards kumiko-guards guards
177
196
  run_guard_bin checks kumiko-guards checks
178
197
  run_guard_bin comment-lang kumiko-guards comment-lang --touched --base=origin/main
198
+ run_guard_bin public-exports-changelog kumiko-guards public-exports-changelog --base=origin/main
179
199
  if [ -n "$FAILED" ]; then
180
200
  echo "[pre-push] worktree check failed:$FAILED" >&2
181
201
  exit 1
@@ -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"];