@westbayberry/dg 2.0.10 → 2.1.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.
Files changed (43) hide show
  1. package/dist/api/analyze.js +5 -3
  2. package/dist/bin/dg.js +1 -1
  3. package/dist/commands/completion.js +2 -1
  4. package/dist/commands/config.js +11 -3
  5. package/dist/commands/decisions.js +155 -0
  6. package/dist/commands/explain.js +6 -2
  7. package/dist/commands/router.js +2 -0
  8. package/dist/commands/scan.js +2 -1
  9. package/dist/commands/status.js +5 -2
  10. package/dist/config/settings.js +144 -25
  11. package/dist/decisions/apply.js +128 -0
  12. package/dist/decisions/remember-prompt.js +97 -0
  13. package/dist/install-ui/block-render.js +21 -4
  14. package/dist/install-ui/prompt.js +14 -0
  15. package/dist/launcher/install-preflight.js +126 -13
  16. package/dist/launcher/preflight-prompt.js +29 -2
  17. package/dist/launcher/run.js +14 -3
  18. package/dist/policy/cooldown.js +104 -0
  19. package/dist/policy/evaluate.js +0 -15
  20. package/dist/presentation/provenance.js +23 -0
  21. package/dist/project/dgfile.js +307 -0
  22. package/dist/proxy/enforcement.js +2 -1
  23. package/dist/proxy/metadata-map.js +25 -1
  24. package/dist/proxy/server.js +31 -2
  25. package/dist/scan/collect.js +10 -4
  26. package/dist/scan/command.js +35 -8
  27. package/dist/scan/discovery.js +66 -4
  28. package/dist/scan/render.js +35 -4
  29. package/dist/scan/scanner-report.js +31 -4
  30. package/dist/scan/staged.js +69 -10
  31. package/dist/scan-ui/LegacyApp.js +4 -4
  32. package/dist/scan-ui/components/InteractiveResultsView.js +64 -7
  33. package/dist/scan-ui/hooks/useScan.js +31 -3
  34. package/dist/scan-ui/launch.js +4 -1
  35. package/dist/scan-ui/shims.js +3 -0
  36. package/dist/scripts/detect.js +153 -0
  37. package/dist/scripts/gate.js +170 -0
  38. package/dist/scripts/rebuild.js +28 -0
  39. package/dist/setup/plan.js +36 -1
  40. package/dist/util/json-file.js +24 -0
  41. package/dist/util/tty-prompt.js +13 -6
  42. package/dist/verify/package-check.js +12 -0
  43. package/package.json +9 -1
@@ -8,6 +8,7 @@ import { dgVersion } from "../commands/version.js";
8
8
  import { compareVersions, readLatestVersion } from "../commands/update.js";
9
9
  import { AuthError, authStatus, displayTier, readAuthState } from "../auth/store.js";
10
10
  import { ConfigError, loadUserConfig } from "../config/settings.js";
11
+ import { describeCooldownSettings } from "../policy/cooldown.js";
11
12
  import { resolveRealBinary } from "../launcher/resolve-real-binary.js";
12
13
  import { packageManagerNames } from "../launcher/classify.js";
13
14
  import { readServiceState } from "../service/state.js";
@@ -42,6 +43,7 @@ const DOCTOR_GROUP_BY_NAME = {
42
43
  "cleanup-registry-stale-entries": "setup",
43
44
  config: "setup",
44
45
  policy: "setup",
46
+ "script-gate": "setup",
45
47
  telemetry: "setup",
46
48
  shims: "setup",
47
49
  "shell-rc": "setup",
@@ -275,6 +277,7 @@ export function doctorReport(options = {}) {
275
277
  checks.push(configCheck(paths, env));
276
278
  checks.push(authCheck(env));
277
279
  checks.push(policyCheck(env));
280
+ checks.push(scriptGateCheck(env));
278
281
  checks.push(telemetryCheck(env));
279
282
  const missingShims = SHIM_COMMANDS.filter((command) => !validShim(join(shimDir, command), command));
280
283
  checks.push({
@@ -1090,7 +1093,7 @@ function policyCheck(env) {
1090
1093
  return {
1091
1094
  name: "policy",
1092
1095
  status: "pass",
1093
- message: `Local policy mode ${config.policy.mode}; project allowlists trusted: ${config.policy.trustProjectAllowlists}`
1096
+ message: `Local policy mode ${config.policy.mode}; project allowlists trusted: ${config.policy.trustProjectAllowlists}; release cooldown ${describeCooldownSettings(config, env)}`
1094
1097
  };
1095
1098
  }
1096
1099
  catch (error) {
@@ -1101,6 +1104,38 @@ function policyCheck(env) {
1101
1104
  };
1102
1105
  }
1103
1106
  }
1107
+ function scriptGateCheck(env) {
1108
+ try {
1109
+ const mode = loadUserConfig(env).scriptGate.mode;
1110
+ if (mode === "off") {
1111
+ return {
1112
+ name: "script-gate",
1113
+ status: "pass",
1114
+ message: "Install-script gate is off; protected installs do not report script-running packages"
1115
+ };
1116
+ }
1117
+ if (mode === "enforce") {
1118
+ return {
1119
+ name: "script-gate",
1120
+ status: "warn",
1121
+ message: "scriptGate.mode is enforce, but this dg release observes only — installs report script-running packages without blocking",
1122
+ fix: "enforcement ships in an upcoming release; dg config set scriptGate.mode observe clears this warning"
1123
+ };
1124
+ }
1125
+ return {
1126
+ name: "script-gate",
1127
+ status: "pass",
1128
+ message: "Install-script gate observes: protected npm/yarn installs report packages that ran install scripts (pnpm blocks them natively)"
1129
+ };
1130
+ }
1131
+ catch (error) {
1132
+ return {
1133
+ name: "script-gate",
1134
+ status: "fail",
1135
+ message: error instanceof ConfigError ? error.message : "Unable to read script gate config"
1136
+ };
1137
+ }
1138
+ }
1104
1139
  function telemetryCheck(env) {
1105
1140
  try {
1106
1141
  const config = loadUserConfig(env);
@@ -0,0 +1,24 @@
1
+ import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
+ import { randomUUID } from "node:crypto";
3
+ import { dirname } from "node:path";
4
+ export function writeJsonAtomic(path, value, options = {}) {
5
+ mkdirSync(dirname(path), {
6
+ recursive: true,
7
+ mode: options.dirMode ?? 0o700
8
+ });
9
+ const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
10
+ try {
11
+ writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, {
12
+ encoding: "utf8",
13
+ flag: "wx",
14
+ mode: options.fileMode ?? 0o600
15
+ });
16
+ renameSync(tempPath, path);
17
+ }
18
+ catch (error) {
19
+ rmSync(tempPath, {
20
+ force: true
21
+ });
22
+ throw error;
23
+ }
24
+ }
@@ -1,5 +1,16 @@
1
1
  import { closeSync, openSync, readSync } from "node:fs";
2
2
  export function promptYesNo(question, defaultYes, out = process.stderr) {
3
+ const answer = promptLine(`${question} ${defaultYes ? "[Y/n]" : "[y/N]"} `, out);
4
+ if (answer === null) {
5
+ return null;
6
+ }
7
+ const normalized = answer.trim().toLowerCase();
8
+ if (normalized === "") {
9
+ return defaultYes;
10
+ }
11
+ return normalized === "y" || normalized === "yes";
12
+ }
13
+ export function promptLine(question, out = process.stderr) {
3
14
  let tty;
4
15
  try {
5
16
  tty = openSync("/dev/tty", "rs");
@@ -8,7 +19,7 @@ export function promptYesNo(question, defaultYes, out = process.stderr) {
8
19
  return null;
9
20
  }
10
21
  try {
11
- out.write(`${question} ${defaultYes ? "[Y/n]" : "[y/N]"} `);
22
+ out.write(question);
12
23
  const byte = Buffer.alloc(1);
13
24
  let answer = "";
14
25
  for (;;) {
@@ -31,11 +42,7 @@ export function promptYesNo(question, defaultYes, out = process.stderr) {
31
42
  }
32
43
  answer += char;
33
44
  }
34
- const normalized = answer.trim().toLowerCase();
35
- if (normalized === "") {
36
- return defaultYes;
37
- }
38
- return normalized === "y" || normalized === "yes";
45
+ return answer;
39
46
  }
40
47
  finally {
41
48
  closeSync(tty);
@@ -2,6 +2,7 @@ import { existsSync, writeFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { analyzePackages, AnalyzeError } from "../api/analyze.js";
4
4
  import { createTheme } from "../presentation/theme.js";
5
+ import { provenanceLabel, provenanceDowngradeLine } from "../presentation/provenance.js";
5
6
  import { resolvePresentation } from "../presentation/mode.js";
6
7
  import { isRemotePackageSpec, isSupportedLockfilePath } from "./preflight.js";
7
8
  import { authStatus } from "../auth/store.js";
@@ -62,6 +63,16 @@ function renderResult(spec, version, result, theme, verbose) {
62
63
  const action = result.action ?? "pass";
63
64
  const badge = theme.badge(action);
64
65
  const lines = [`${badge} ${result.name}@${version} (${spec.ecosystem}) ${theme.paint("muted", `score ${result.score}`)}`];
66
+ if (result.provenance) {
67
+ const label = verbose && result.provenance.predicateType
68
+ ? `provenance ${provenanceLabel(result.provenance)} · ${result.provenance.predicateType}`
69
+ : `provenance ${provenanceLabel(result.provenance)}`;
70
+ lines.push(` ${theme.paint("muted", label)}`);
71
+ const downgrade = provenanceDowngradeLine(version, result.provenance);
72
+ if (downgrade) {
73
+ lines.push(` ${theme.paint("warn", `⚠ ${downgrade}`)}`);
74
+ }
75
+ }
65
76
  const reasons = verbose ? result.reasons : result.reasons.slice(0, 6);
66
77
  for (const reason of reasons) {
67
78
  const glyph = action === "block" ? theme.paint("block", "✘") : action === "warn" ? theme.paint("warn", "⚠") : theme.paint("muted", "·");
@@ -157,6 +168,7 @@ export async function runPackageCheck(target, io = {}, options = {}) {
157
168
  score: result.score,
158
169
  reasons: result.reasons,
159
170
  findings: result.findings,
171
+ ...(result.provenance ? { provenance: result.provenance } : {}),
160
172
  ...(result.recommendation ? { recommendation: result.recommendation } : {})
161
173
  }, null, 2)}\n`
162
174
  : renderResult(parsed, version, result, theme, options.verbose ?? false);
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@westbayberry/dg",
3
- "version": "2.0.10",
3
+ "version": "2.1.0",
4
4
  "description": "Dependency Guardian supply-chain firewall CLI",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/WestBayBerry/DG_CLI.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/WestBayBerry/DG_CLI/issues"
11
+ },
12
+ "homepage": "https://github.com/WestBayBerry/DG_CLI#readme",
5
13
  "type": "module",
6
14
  "bin": {
7
15
  "dg": "./dist/bin/dg.js"