@zivis/cli 0.1.0-alpha.4 → 0.1.0-alpha.41

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 (180) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +31 -0
  3. package/dist/commands/app/index.d.ts +0 -8
  4. package/dist/commands/app/index.js +8 -18
  5. package/dist/commands/assurance/index.d.ts +33 -0
  6. package/dist/commands/assurance/index.js +149 -0
  7. package/dist/commands/auth/bind.d.ts +4 -0
  8. package/dist/commands/auth/bind.js +97 -0
  9. package/dist/commands/auth/index.js +21 -2
  10. package/dist/commands/auth/init.d.ts +2 -0
  11. package/dist/commands/auth/init.js +249 -73
  12. package/dist/commands/auth/login.d.ts +2 -0
  13. package/dist/commands/auth/login.js +136 -18
  14. package/dist/commands/auth/logout.js +19 -7
  15. package/dist/commands/auth/sessions.js +7 -4
  16. package/dist/commands/auth/status.js +33 -12
  17. package/dist/commands/auth/token.d.ts +4 -0
  18. package/dist/commands/auth/token.js +73 -0
  19. package/dist/commands/check/index.d.ts +2 -0
  20. package/dist/commands/check/index.js +463 -0
  21. package/dist/commands/credits/index.js +2 -3
  22. package/dist/commands/gate/index.d.ts +2 -0
  23. package/dist/commands/gate/index.js +171 -0
  24. package/dist/commands/inspect/index.d.ts +2 -0
  25. package/dist/commands/inspect/index.js +136 -0
  26. package/dist/commands/mcp/index.js +113 -14
  27. package/dist/commands/open/index.d.ts +0 -8
  28. package/dist/commands/open/index.js +0 -11
  29. package/dist/commands/run/index.d.ts +2 -0
  30. package/dist/commands/run/index.js +111 -0
  31. package/dist/commands/scan/index.js +3 -8
  32. package/dist/commands/sync/index.d.ts +2 -0
  33. package/dist/commands/sync/index.js +187 -0
  34. package/dist/commands/target/index.js +0 -3
  35. package/dist/commands/test/index.d.ts +2 -0
  36. package/dist/commands/test/index.js +115 -0
  37. package/dist/commands/tm/index.js +7 -10
  38. package/dist/index.js +24 -1
  39. package/dist/internal/application-binding.d.ts +8 -0
  40. package/dist/internal/application-binding.js +167 -0
  41. package/dist/internal/binding-token-mint.d.ts +33 -0
  42. package/dist/internal/binding-token-mint.js +174 -0
  43. package/dist/internal/cli-output.d.ts +16 -0
  44. package/dist/internal/cli-output.js +39 -0
  45. package/dist/internal/devx-run.d.ts +47 -0
  46. package/dist/internal/devx-run.js +36 -0
  47. package/dist/internal/extractor/adapters/go/index.d.ts +0 -4
  48. package/dist/internal/extractor/adapters/go/index.js +0 -6
  49. package/dist/internal/extractor/adapters/go/manifest.d.ts +0 -17
  50. package/dist/internal/extractor/adapters/go/manifest.js +19 -25
  51. package/dist/internal/extractor/adapters/go/parser.d.ts +0 -4
  52. package/dist/internal/extractor/adapters/go/parser.js +0 -5
  53. package/dist/internal/extractor/adapters/go/scanner.d.ts +0 -20
  54. package/dist/internal/extractor/adapters/go/scanner.js +0 -43
  55. package/dist/internal/extractor/adapters/python/index.d.ts +0 -3
  56. package/dist/internal/extractor/adapters/python/index.js +0 -4
  57. package/dist/internal/extractor/adapters/python/manifest.d.ts +0 -8
  58. package/dist/internal/extractor/adapters/python/manifest.js +21 -18
  59. package/dist/internal/extractor/adapters/python/parser.d.ts +0 -3
  60. package/dist/internal/extractor/adapters/python/parser.js +0 -4
  61. package/dist/internal/extractor/adapters/python/scanner.d.ts +0 -18
  62. package/dist/internal/extractor/adapters/python/scanner.js +1 -54
  63. package/dist/internal/extractor/adapters/typescript/index.d.ts +0 -7
  64. package/dist/internal/extractor/adapters/typescript/index.js +1 -21
  65. package/dist/internal/extractor/adapters/typescript/manifest.d.ts +0 -14
  66. package/dist/internal/extractor/adapters/typescript/manifest.js +26 -17
  67. package/dist/internal/extractor/adapters/typescript/parser.d.ts +0 -8
  68. package/dist/internal/extractor/adapters/typescript/parser.js +0 -9
  69. package/dist/internal/extractor/adapters/typescript/scanner.d.ts +0 -24
  70. package/dist/internal/extractor/adapters/typescript/scanner.js +0 -43
  71. package/dist/internal/extractor/graph-builder.d.ts +10 -0
  72. package/dist/internal/extractor/graph-builder.js +245 -0
  73. package/dist/internal/extractor/graph-types.d.ts +89 -0
  74. package/dist/internal/extractor/graph-types.js +1 -0
  75. package/dist/internal/extractor/index.d.ts +0 -14
  76. package/dist/internal/extractor/index.js +0 -13
  77. package/dist/internal/extractor/types.d.ts +0 -45
  78. package/dist/internal/extractor/types.js +0 -27
  79. package/dist/internal/extractor/walker.d.ts +0 -18
  80. package/dist/internal/extractor/walker.js +0 -18
  81. package/dist/internal/gate-evaluate.d.ts +50 -0
  82. package/dist/internal/gate-evaluate.js +111 -0
  83. package/dist/internal/gate-policy.d.ts +38 -0
  84. package/dist/internal/gate-policy.js +167 -0
  85. package/dist/internal/git-metadata.d.ts +8 -0
  86. package/dist/internal/git-metadata.js +38 -0
  87. package/dist/internal/git.d.ts +8 -0
  88. package/dist/internal/git.js +30 -0
  89. package/dist/internal/github-linkage-prompt.d.ts +7 -0
  90. package/dist/internal/github-linkage-prompt.js +95 -0
  91. package/dist/internal/ide-setup.d.ts +17 -7
  92. package/dist/internal/ide-setup.js +377 -46
  93. package/dist/internal/inventory-sync.d.ts +74 -0
  94. package/dist/internal/inventory-sync.js +189 -0
  95. package/dist/internal/org-picker.d.ts +11 -0
  96. package/dist/internal/org-picker.js +34 -0
  97. package/dist/internal/orgs.js +0 -1
  98. package/dist/internal/packs/cache.d.ts +8 -0
  99. package/dist/internal/packs/cache.js +60 -0
  100. package/dist/internal/packs/index.d.ts +10 -0
  101. package/dist/internal/packs/index.js +7 -0
  102. package/dist/internal/packs/integrity.d.ts +9 -0
  103. package/dist/internal/packs/integrity.js +24 -0
  104. package/dist/internal/packs/jcs.d.ts +2 -0
  105. package/dist/internal/packs/jcs.js +57 -0
  106. package/dist/internal/packs/local-paths.d.ts +4 -0
  107. package/dist/internal/packs/local-paths.js +26 -0
  108. package/dist/internal/packs/registry-client.d.ts +20 -0
  109. package/dist/internal/packs/registry-client.js +40 -0
  110. package/dist/internal/packs/resolve.d.ts +8 -0
  111. package/dist/internal/packs/resolve.js +89 -0
  112. package/dist/internal/packs/semver.d.ts +9 -0
  113. package/dist/internal/packs/semver.js +57 -0
  114. package/dist/internal/packs/signing.d.ts +5 -0
  115. package/dist/internal/packs/signing.js +49 -0
  116. package/dist/internal/packs/types.d.ts +70 -0
  117. package/dist/internal/packs/types.js +20 -0
  118. package/dist/internal/project-binding.d.ts +1 -0
  119. package/dist/internal/project-binding.js +14 -9
  120. package/dist/internal/run-workdir.d.ts +1 -0
  121. package/dist/internal/run-workdir.js +11 -0
  122. package/dist/internal/security-context.d.ts +62 -0
  123. package/dist/internal/security-context.js +50 -0
  124. package/dist/internal/sync-outbox.d.ts +40 -0
  125. package/dist/internal/sync-outbox.js +66 -0
  126. package/dist/internal/target-auth/api-config.d.ts +0 -17
  127. package/dist/internal/target-auth/api-config.js +0 -18
  128. package/dist/internal/target-auth/config.d.ts +0 -12
  129. package/dist/internal/target-auth/config.js +0 -13
  130. package/dist/internal/target-auth/index.d.ts +0 -18
  131. package/dist/internal/target-auth/index.js +0 -19
  132. package/dist/internal/target-auth/keychain.d.ts +0 -18
  133. package/dist/internal/target-auth/keychain.js +3 -23
  134. package/dist/internal/target-auth/types.d.ts +0 -26
  135. package/dist/internal/target-auth/types.js +0 -10
  136. package/dist/internal/target-auth/workos.d.ts +0 -15
  137. package/dist/internal/target-auth/workos.js +9 -18
  138. package/dist/internal/test-scope.d.ts +68 -0
  139. package/dist/internal/test-scope.js +69 -0
  140. package/dist/internal/zivis-local-state.d.ts +1 -0
  141. package/dist/internal/zivis-local-state.js +22 -0
  142. package/package.json +20 -5
  143. package/dist/commands/app/index.js.map +0 -1
  144. package/dist/commands/auth/index.js.map +0 -1
  145. package/dist/commands/auth/init.js.map +0 -1
  146. package/dist/commands/auth/login.js.map +0 -1
  147. package/dist/commands/auth/logout.js.map +0 -1
  148. package/dist/commands/auth/sessions.js.map +0 -1
  149. package/dist/commands/auth/status.js.map +0 -1
  150. package/dist/commands/credits/index.js.map +0 -1
  151. package/dist/commands/mcp/index.js.map +0 -1
  152. package/dist/commands/open/index.js.map +0 -1
  153. package/dist/commands/scan/index.js.map +0 -1
  154. package/dist/commands/target/index.js.map +0 -1
  155. package/dist/commands/tm/index.js.map +0 -1
  156. package/dist/index.js.map +0 -1
  157. package/dist/internal/extractor/adapters/go/index.js.map +0 -1
  158. package/dist/internal/extractor/adapters/go/manifest.js.map +0 -1
  159. package/dist/internal/extractor/adapters/go/parser.js.map +0 -1
  160. package/dist/internal/extractor/adapters/go/scanner.js.map +0 -1
  161. package/dist/internal/extractor/adapters/python/index.js.map +0 -1
  162. package/dist/internal/extractor/adapters/python/manifest.js.map +0 -1
  163. package/dist/internal/extractor/adapters/python/parser.js.map +0 -1
  164. package/dist/internal/extractor/adapters/python/scanner.js.map +0 -1
  165. package/dist/internal/extractor/adapters/typescript/index.js.map +0 -1
  166. package/dist/internal/extractor/adapters/typescript/manifest.js.map +0 -1
  167. package/dist/internal/extractor/adapters/typescript/parser.js.map +0 -1
  168. package/dist/internal/extractor/adapters/typescript/scanner.js.map +0 -1
  169. package/dist/internal/extractor/index.js.map +0 -1
  170. package/dist/internal/extractor/types.js.map +0 -1
  171. package/dist/internal/extractor/walker.js.map +0 -1
  172. package/dist/internal/ide-setup.js.map +0 -1
  173. package/dist/internal/orgs.js.map +0 -1
  174. package/dist/internal/project-binding.js.map +0 -1
  175. package/dist/internal/target-auth/api-config.js.map +0 -1
  176. package/dist/internal/target-auth/config.js.map +0 -1
  177. package/dist/internal/target-auth/index.js.map +0 -1
  178. package/dist/internal/target-auth/keychain.js.map +0 -1
  179. package/dist/internal/target-auth/types.js.map +0 -1
  180. package/dist/internal/target-auth/workos.js.map +0 -1
@@ -0,0 +1,50 @@
1
+ import type { GatePolicy } from "./gate-policy.js";
2
+ export type CheckResult = "pass" | "fail" | "indeterminate";
3
+ export interface GateCheck {
4
+ type: string;
5
+ category_id?: string;
6
+ actual: number | string | boolean | null;
7
+ required: number | string | boolean;
8
+ result: CheckResult;
9
+ }
10
+ export interface AssuranceStatusForGate {
11
+ model: {
12
+ key: string;
13
+ };
14
+ evaluatedAt: string | null;
15
+ lastEvaluatedGitSha: string | null;
16
+ overall: {
17
+ scoreRaw: number | null;
18
+ coverage: number | null;
19
+ };
20
+ categories: Array<{
21
+ categoryId: string;
22
+ scoreRaw: number | null;
23
+ coverage: number | null;
24
+ }>;
25
+ risk: {
26
+ openCritical: number;
27
+ openHigh: number;
28
+ };
29
+ }
30
+ export interface MarkForGate {
31
+ basis: string | null;
32
+ expired: boolean;
33
+ revoked: boolean;
34
+ signature: {
35
+ verified: boolean;
36
+ };
37
+ }
38
+ export interface GateEvaluationInput {
39
+ policy: GatePolicy;
40
+ status: AssuranceStatusForGate | null;
41
+ mark: MarkForGate | null;
42
+ markLookupFailed: boolean;
43
+ currentGitSha: string | undefined;
44
+ now: Date;
45
+ }
46
+ export interface GateEvaluationResult {
47
+ result: "pass" | "fail" | "indeterminate";
48
+ checks: GateCheck[];
49
+ }
50
+ export declare function evaluateGate(input: GateEvaluationInput): GateEvaluationResult;
@@ -0,0 +1,111 @@
1
+ import { parseDurationMs } from "./gate-policy.js";
2
+ export function evaluateGate(input) {
3
+ const checks = [];
4
+ const { policy, status, mark, markLookupFailed, currentGitSha, now } = input;
5
+ if (!status) {
6
+ return {
7
+ result: "indeterminate",
8
+ checks: [{ type: "assurance_status", actual: null, required: "present", result: "indeterminate" }],
9
+ };
10
+ }
11
+ if (policy.assurance?.overall?.min_score_raw !== undefined) {
12
+ checks.push(scoreCheck("overall_score", undefined, status.overall.scoreRaw, policy.assurance.overall.min_score_raw));
13
+ }
14
+ if (policy.assurance?.overall?.min_coverage !== undefined) {
15
+ checks.push(scoreCheck("overall_coverage", undefined, status.overall.coverage, policy.assurance.overall.min_coverage));
16
+ }
17
+ for (const cat of policy.assurance?.categories ?? []) {
18
+ const found = status.categories.find((c) => c.categoryId === cat.id);
19
+ if (cat.min_score_raw !== undefined) {
20
+ checks.push(scoreCheck("category_score", cat.id, found ? found.scoreRaw : null, cat.min_score_raw));
21
+ }
22
+ if (cat.min_coverage !== undefined) {
23
+ checks.push(scoreCheck("category_coverage", cat.id, found ? found.coverage : null, cat.min_coverage));
24
+ }
25
+ }
26
+ if (policy.risk?.max_open_critical !== undefined) {
27
+ checks.push(countCeilingCheck("open_critical_risks", status.risk.openCritical, policy.risk.max_open_critical));
28
+ }
29
+ if (policy.risk?.max_open_high !== undefined) {
30
+ checks.push(countCeilingCheck("open_high_risks", status.risk.openHigh, policy.risk.max_open_high));
31
+ }
32
+ if (policy.freshness?.max_age !== undefined) {
33
+ checks.push(freshnessAgeCheck(status.evaluatedAt, policy.freshness.max_age, now));
34
+ }
35
+ if (policy.freshness?.require_current_git_sha) {
36
+ checks.push(freshnessGitShaCheck(status.lastEvaluatedGitSha, currentGitSha));
37
+ }
38
+ if (policy.provenance?.allowed_basis !== undefined) {
39
+ checks.push(provenanceBasisCheck(mark, markLookupFailed, policy.provenance.allowed_basis));
40
+ }
41
+ if (policy.provenance?.require_signed_mark) {
42
+ checks.push(provenanceSignedMarkCheck(mark, markLookupFailed));
43
+ }
44
+ const overall = combine(checks);
45
+ return { result: overall, checks };
46
+ }
47
+ function combine(checks) {
48
+ if (checks.length === 0)
49
+ return "pass";
50
+ if (checks.some((c) => c.result === "indeterminate"))
51
+ return "indeterminate";
52
+ if (checks.some((c) => c.result === "fail"))
53
+ return "fail";
54
+ return "pass";
55
+ }
56
+ function scoreCheck(type, categoryId, actual, required) {
57
+ return {
58
+ type,
59
+ ...(categoryId ? { category_id: categoryId } : {}),
60
+ actual,
61
+ required,
62
+ result: actual === null ? "indeterminate" : actual >= required ? "pass" : "fail",
63
+ };
64
+ }
65
+ function countCeilingCheck(type, actual, maxAllowed) {
66
+ return { type, actual, required: maxAllowed, result: actual <= maxAllowed ? "pass" : "fail" };
67
+ }
68
+ function freshnessAgeCheck(evaluatedAt, maxAge, now) {
69
+ if (!evaluatedAt) {
70
+ return { type: "freshness_max_age", actual: null, required: maxAge, result: "indeterminate" };
71
+ }
72
+ const maxAgeMs = parseDurationMs(maxAge);
73
+ if (maxAgeMs === null) {
74
+ return { type: "freshness_max_age", actual: evaluatedAt, required: maxAge, result: "indeterminate" };
75
+ }
76
+ const ageMs = now.getTime() - new Date(evaluatedAt).getTime();
77
+ return { type: "freshness_max_age", actual: `${Math.round(ageMs / 1000)}s`, required: maxAge, result: ageMs <= maxAgeMs ? "pass" : "indeterminate" };
78
+ }
79
+ function freshnessGitShaCheck(lastEvaluatedGitSha, currentGitSha) {
80
+ const matches = !!lastEvaluatedGitSha && !!currentGitSha && lastEvaluatedGitSha === currentGitSha;
81
+ return {
82
+ type: "freshness_current_git_sha",
83
+ actual: lastEvaluatedGitSha,
84
+ required: currentGitSha ?? "HEAD",
85
+ result: matches ? "pass" : "indeterminate",
86
+ };
87
+ }
88
+ function provenanceBasisCheck(mark, markLookupFailed, allowedBasis) {
89
+ if (markLookupFailed) {
90
+ return { type: "provenance_basis", actual: null, required: allowedBasis.join(","), result: "indeterminate" };
91
+ }
92
+ if (!mark || !mark.basis) {
93
+ return { type: "provenance_basis", actual: null, required: allowedBasis.join(","), result: "fail" };
94
+ }
95
+ return {
96
+ type: "provenance_basis",
97
+ actual: mark.basis,
98
+ required: allowedBasis.join(","),
99
+ result: allowedBasis.includes(mark.basis) ? "pass" : "fail",
100
+ };
101
+ }
102
+ function provenanceSignedMarkCheck(mark, markLookupFailed) {
103
+ if (markLookupFailed) {
104
+ return { type: "provenance_signed_mark", actual: null, required: true, result: "indeterminate" };
105
+ }
106
+ if (!mark) {
107
+ return { type: "provenance_signed_mark", actual: false, required: true, result: "fail" };
108
+ }
109
+ const valid = mark.signature.verified && !mark.expired && !mark.revoked;
110
+ return { type: "provenance_signed_mark", actual: valid, required: true, result: valid ? "pass" : "fail" };
111
+ }
@@ -0,0 +1,38 @@
1
+ export interface CategoryThreshold {
2
+ id: string;
3
+ min_score_raw?: number;
4
+ min_coverage?: number;
5
+ }
6
+ export interface GatePolicy {
7
+ source: "current";
8
+ model: string;
9
+ scoring_profile?: string;
10
+ assurance?: {
11
+ overall?: {
12
+ min_score_raw?: number;
13
+ min_coverage?: number;
14
+ };
15
+ categories?: CategoryThreshold[];
16
+ };
17
+ risk?: {
18
+ max_open_critical?: number;
19
+ max_open_high?: number;
20
+ };
21
+ freshness?: {
22
+ max_age?: string;
23
+ require_current_git_sha?: boolean;
24
+ };
25
+ provenance?: {
26
+ allowed_basis?: string[];
27
+ require_signed_mark?: boolean;
28
+ };
29
+ }
30
+ export interface PolicyFile {
31
+ version: 1;
32
+ gates: Record<string, GatePolicy>;
33
+ }
34
+ export declare class PolicyValidationError extends Error {
35
+ constructor(message: string);
36
+ }
37
+ export declare function parsePolicyFile(rawYaml: string): PolicyFile;
38
+ export declare function parseDurationMs(spec: string): number | null;
@@ -0,0 +1,167 @@
1
+ import * as yaml from "js-yaml";
2
+ export class PolicyValidationError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "PolicyValidationError";
6
+ }
7
+ }
8
+ const TOP_LEVEL_KEYS = new Set(["version", "gates"]);
9
+ const GATE_KEYS = new Set(["source", "model", "scoring_profile", "assurance", "risk", "freshness", "provenance"]);
10
+ const ASSURANCE_KEYS = new Set(["overall", "categories"]);
11
+ const SCORE_KEYS = new Set(["min_score_raw", "min_coverage"]);
12
+ const CATEGORY_KEYS = new Set(["id", "min_score_raw", "min_coverage"]);
13
+ const RISK_KEYS = new Set(["max_open_critical", "max_open_high"]);
14
+ const FRESHNESS_KEYS = new Set(["max_age", "require_current_git_sha"]);
15
+ const PROVENANCE_KEYS = new Set(["allowed_basis", "require_signed_mark"]);
16
+ function rejectUnknown(obj, allowed, where) {
17
+ for (const key of Object.keys(obj)) {
18
+ if (!allowed.has(key)) {
19
+ throw new PolicyValidationError(`Unknown policy field '${where}.${key}' — v1 does not recognize this key.`);
20
+ }
21
+ }
22
+ }
23
+ function isPlainObject(v) {
24
+ return typeof v === "object" && v !== null && !Array.isArray(v);
25
+ }
26
+ export function parsePolicyFile(rawYaml) {
27
+ let doc;
28
+ try {
29
+ doc = yaml.load(rawYaml);
30
+ }
31
+ catch (err) {
32
+ throw new PolicyValidationError(`Could not parse policy YAML: ${err instanceof Error ? err.message : String(err)}`);
33
+ }
34
+ if (!isPlainObject(doc)) {
35
+ throw new PolicyValidationError("Policy file must be a YAML mapping at the top level.");
36
+ }
37
+ rejectUnknown(doc, TOP_LEVEL_KEYS, "$");
38
+ if (doc.version !== 1) {
39
+ throw new PolicyValidationError(`Unsupported policy 'version': ${JSON.stringify(doc.version)} — only version 1 is recognized.`);
40
+ }
41
+ if (!isPlainObject(doc.gates)) {
42
+ throw new PolicyValidationError("Policy file must declare a 'gates' mapping.");
43
+ }
44
+ const gates = {};
45
+ for (const [name, raw] of Object.entries(doc.gates)) {
46
+ gates[name] = parseGate(name, raw);
47
+ }
48
+ return { version: 1, gates };
49
+ }
50
+ function parseGate(name, raw) {
51
+ if (!isPlainObject(raw)) {
52
+ throw new PolicyValidationError(`Gate '${name}' must be a YAML mapping.`);
53
+ }
54
+ rejectUnknown(raw, GATE_KEYS, `gates.${name}`);
55
+ if (raw.source !== "current") {
56
+ throw new PolicyValidationError(`Gate '${name}.source' must be 'current' — only live current-state gates are supported in v1.`);
57
+ }
58
+ if (typeof raw.model !== "string" || !raw.model) {
59
+ throw new PolicyValidationError(`Gate '${name}.model' is required and must be a string.`);
60
+ }
61
+ if (raw.scoring_profile !== undefined && typeof raw.scoring_profile !== "string") {
62
+ throw new PolicyValidationError(`Gate '${name}.scoring_profile' must be a string.`);
63
+ }
64
+ const policy = { source: "current", model: raw.model };
65
+ if (typeof raw.scoring_profile === "string")
66
+ policy.scoring_profile = raw.scoring_profile;
67
+ if (raw.assurance !== undefined) {
68
+ if (!isPlainObject(raw.assurance))
69
+ throw new PolicyValidationError(`Gate '${name}.assurance' must be a mapping.`);
70
+ rejectUnknown(raw.assurance, ASSURANCE_KEYS, `gates.${name}.assurance`);
71
+ policy.assurance = {};
72
+ if (raw.assurance.overall !== undefined) {
73
+ if (!isPlainObject(raw.assurance.overall))
74
+ throw new PolicyValidationError(`Gate '${name}.assurance.overall' must be a mapping.`);
75
+ rejectUnknown(raw.assurance.overall, SCORE_KEYS, `gates.${name}.assurance.overall`);
76
+ policy.assurance.overall = parseScoreThreshold(raw.assurance.overall, `${name}.assurance.overall`);
77
+ }
78
+ if (raw.assurance.categories !== undefined) {
79
+ if (!Array.isArray(raw.assurance.categories))
80
+ throw new PolicyValidationError(`Gate '${name}.assurance.categories' must be a list.`);
81
+ policy.assurance.categories = raw.assurance.categories.map((c, i) => {
82
+ if (!isPlainObject(c))
83
+ throw new PolicyValidationError(`Gate '${name}.assurance.categories[${i}]' must be a mapping.`);
84
+ rejectUnknown(c, CATEGORY_KEYS, `gates.${name}.assurance.categories[${i}]`);
85
+ if (typeof c.id !== "string" || !c.id) {
86
+ throw new PolicyValidationError(`Gate '${name}.assurance.categories[${i}].id' is required and must be a string.`);
87
+ }
88
+ return { id: c.id, ...parseScoreThreshold(c, `${name}.assurance.categories[${i}]`) };
89
+ });
90
+ }
91
+ }
92
+ if (raw.risk !== undefined) {
93
+ if (!isPlainObject(raw.risk))
94
+ throw new PolicyValidationError(`Gate '${name}.risk' must be a mapping.`);
95
+ rejectUnknown(raw.risk, RISK_KEYS, `gates.${name}.risk`);
96
+ policy.risk = {};
97
+ if (raw.risk.max_open_critical !== undefined)
98
+ policy.risk.max_open_critical = requireNonNegativeInt(raw.risk.max_open_critical, `${name}.risk.max_open_critical`);
99
+ if (raw.risk.max_open_high !== undefined)
100
+ policy.risk.max_open_high = requireNonNegativeInt(raw.risk.max_open_high, `${name}.risk.max_open_high`);
101
+ }
102
+ if (raw.freshness !== undefined) {
103
+ if (!isPlainObject(raw.freshness))
104
+ throw new PolicyValidationError(`Gate '${name}.freshness' must be a mapping.`);
105
+ rejectUnknown(raw.freshness, FRESHNESS_KEYS, `gates.${name}.freshness`);
106
+ policy.freshness = {};
107
+ if (raw.freshness.max_age !== undefined) {
108
+ if (typeof raw.freshness.max_age !== "string" || parseDurationMs(raw.freshness.max_age) === null) {
109
+ throw new PolicyValidationError(`Gate '${name}.freshness.max_age' must be a duration like '14d', '24h', or '30m'.`);
110
+ }
111
+ policy.freshness.max_age = raw.freshness.max_age;
112
+ }
113
+ if (raw.freshness.require_current_git_sha !== undefined) {
114
+ if (typeof raw.freshness.require_current_git_sha !== "boolean") {
115
+ throw new PolicyValidationError(`Gate '${name}.freshness.require_current_git_sha' must be true or false.`);
116
+ }
117
+ policy.freshness.require_current_git_sha = raw.freshness.require_current_git_sha;
118
+ }
119
+ }
120
+ if (raw.provenance !== undefined) {
121
+ if (!isPlainObject(raw.provenance))
122
+ throw new PolicyValidationError(`Gate '${name}.provenance' must be a mapping.`);
123
+ rejectUnknown(raw.provenance, PROVENANCE_KEYS, `gates.${name}.provenance`);
124
+ policy.provenance = {};
125
+ if (raw.provenance.allowed_basis !== undefined) {
126
+ if (!Array.isArray(raw.provenance.allowed_basis) || !raw.provenance.allowed_basis.every((b) => typeof b === "string")) {
127
+ throw new PolicyValidationError(`Gate '${name}.provenance.allowed_basis' must be a list of strings.`);
128
+ }
129
+ policy.provenance.allowed_basis = raw.provenance.allowed_basis;
130
+ }
131
+ if (raw.provenance.require_signed_mark !== undefined) {
132
+ if (typeof raw.provenance.require_signed_mark !== "boolean") {
133
+ throw new PolicyValidationError(`Gate '${name}.provenance.require_signed_mark' must be true or false.`);
134
+ }
135
+ policy.provenance.require_signed_mark = raw.provenance.require_signed_mark;
136
+ }
137
+ }
138
+ return policy;
139
+ }
140
+ function parseScoreThreshold(obj, where) {
141
+ const out = {};
142
+ if (obj.min_score_raw !== undefined)
143
+ out.min_score_raw = requireUnitInterval(obj.min_score_raw, `${where}.min_score_raw`);
144
+ if (obj.min_coverage !== undefined)
145
+ out.min_coverage = requireUnitInterval(obj.min_coverage, `${where}.min_coverage`);
146
+ return out;
147
+ }
148
+ function requireUnitInterval(v, where) {
149
+ if (typeof v !== "number" || Number.isNaN(v) || v < 0 || v > 1) {
150
+ throw new PolicyValidationError(`'${where}' must be a number between 0.0 and 1.0 (normalized score_raw — never 0-10 or 0-1000).`);
151
+ }
152
+ return v;
153
+ }
154
+ function requireNonNegativeInt(v, where) {
155
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 0) {
156
+ throw new PolicyValidationError(`'${where}' must be a non-negative integer.`);
157
+ }
158
+ return v;
159
+ }
160
+ const DURATION_RE = /^(\d+)(d|h|m)$/;
161
+ const DURATION_UNIT_MS = { d: 86_400_000, h: 3_600_000, m: 60_000 };
162
+ export function parseDurationMs(spec) {
163
+ const m = DURATION_RE.exec(spec.trim());
164
+ if (!m)
165
+ return null;
166
+ return Number(m[1]) * DURATION_UNIT_MS[m[2]];
167
+ }
@@ -0,0 +1,8 @@
1
+ export interface GitMetadata {
2
+ remoteUrl?: string;
3
+ commitSha?: string;
4
+ branch?: string;
5
+ }
6
+ export declare function readGitMetadata(rootDir: string): Promise<GitMetadata>;
7
+ export declare function deriveRepoFullNameFromRemote(remoteUrl: string | undefined): string | undefined;
8
+ export declare function deriveRepoNameFromGit(remoteUrl: string | undefined, rootDir: string): string | undefined;
@@ -0,0 +1,38 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import * as path from "node:path";
4
+ const execAsync = promisify(exec);
5
+ export async function readGitMetadata(rootDir) {
6
+ return {
7
+ remoteUrl: await tryGit(rootDir, "remote get-url origin"),
8
+ commitSha: await tryGit(rootDir, "rev-parse HEAD"),
9
+ branch: await tryGit(rootDir, "rev-parse --abbrev-ref HEAD"),
10
+ };
11
+ }
12
+ async function tryGit(cwd, args) {
13
+ try {
14
+ const { stdout } = await execAsync(`git ${args}`, { cwd });
15
+ const value = stdout.trim();
16
+ return value || undefined;
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ export function deriveRepoFullNameFromRemote(remoteUrl) {
23
+ if (!remoteUrl)
24
+ return undefined;
25
+ const m = /[:/]([^/:]+)\/([^/]+?)(?:\.git)?$/.exec(remoteUrl);
26
+ if (!m)
27
+ return undefined;
28
+ return `${m[1]}/${m[2]}`;
29
+ }
30
+ export function deriveRepoNameFromGit(remoteUrl, rootDir) {
31
+ if (remoteUrl) {
32
+ const m = /[/:]([^/:]+?)\/([^/]+?)(?:\.git)?$/.exec(remoteUrl);
33
+ if (m)
34
+ return m[2];
35
+ }
36
+ const base = path.basename(rootDir);
37
+ return base && base !== "." ? base : undefined;
38
+ }
@@ -0,0 +1,8 @@
1
+ export interface GitMetadata {
2
+ remoteUrl?: string;
3
+ commitSha?: string;
4
+ branch?: string;
5
+ dirty?: boolean;
6
+ }
7
+ export declare function readGitMetadata(rootDir?: string): Promise<GitMetadata>;
8
+ export declare function getCurrentGitSha(rootDir?: string): Promise<string | undefined>;
@@ -0,0 +1,30 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execAsync = promisify(exec);
4
+ export async function readGitMetadata(rootDir = process.cwd()) {
5
+ const [remoteUrl, commitSha, branch, statusOutput] = await Promise.all([
6
+ tryGit(rootDir, "remote get-url origin"),
7
+ tryGit(rootDir, "rev-parse HEAD"),
8
+ tryGit(rootDir, "rev-parse --abbrev-ref HEAD"),
9
+ tryGit(rootDir, "status --porcelain"),
10
+ ]);
11
+ return {
12
+ remoteUrl,
13
+ commitSha,
14
+ branch,
15
+ dirty: statusOutput === undefined ? undefined : statusOutput.length > 0,
16
+ };
17
+ }
18
+ export async function getCurrentGitSha(rootDir = process.cwd()) {
19
+ return tryGit(rootDir, "rev-parse HEAD");
20
+ }
21
+ async function tryGit(cwd, args) {
22
+ try {
23
+ const { stdout } = await execAsync(`git ${args}`, { cwd });
24
+ const value = stdout.trim();
25
+ return value || undefined;
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ }
@@ -0,0 +1,7 @@
1
+ import type { ZivisConfig } from "@zivis/mcp/types";
2
+ export interface BuildLinkagePromptOpts {
3
+ config: ZivisConfig;
4
+ applicationId?: string;
5
+ style?: "short" | "full";
6
+ }
7
+ export declare function buildLinkagePrompt(opts: BuildLinkagePromptOpts): Promise<string | null>;
@@ -0,0 +1,95 @@
1
+ import { resolveAuth } from "@zivis/mcp/auth";
2
+ export async function buildLinkagePrompt(opts) {
3
+ const creds = await resolveAuth(opts.config);
4
+ if (creds.method === "none")
5
+ return null;
6
+ const appStatus = await fetchGitHubAppStatus(opts.config, creds);
7
+ if (!appStatus)
8
+ return null;
9
+ if (!appStatus.installed) {
10
+ if (!appStatus.installUrl)
11
+ return null;
12
+ return formatInstallPrompt(appStatus.installUrl, opts.style ?? "full");
13
+ }
14
+ if (opts.applicationId) {
15
+ const repoStatus = await fetchAppRepoStatus(opts.config, creds, opts.applicationId);
16
+ if (repoStatus && !repoStatus.linked) {
17
+ return formatLinkRepoPrompt(opts.config, opts.applicationId, opts.style ?? "full");
18
+ }
19
+ }
20
+ return null;
21
+ }
22
+ async function fetchGitHubAppStatus(config, creds) {
23
+ try {
24
+ const res = await fetch(`${config.apiBaseUrl}/api/github-app/status`, {
25
+ headers: authHeaders(creds),
26
+ });
27
+ if (!res.ok)
28
+ return null;
29
+ return (await res.json());
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ async function fetchAppRepoStatus(config, creds, applicationId) {
36
+ try {
37
+ const res = await fetch(`${config.apiBaseUrl}/api/applications/${encodeURIComponent(applicationId)}/repos/status`, { headers: authHeaders(creds) });
38
+ if (!res.ok)
39
+ return null;
40
+ return (await res.json());
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ function authHeaders(creds) {
47
+ const base = {
48
+ "Content-Type": "application/json",
49
+ "User-Agent": "@zivis/cli",
50
+ };
51
+ if (creds.method === "oauth" && creds.accessToken) {
52
+ base.Authorization = `Bearer ${creds.accessToken}`;
53
+ }
54
+ else if (creds.method === "apikey" && creds.apiKey) {
55
+ base["X-API-Key"] = creds.apiKey;
56
+ }
57
+ return base;
58
+ }
59
+ function formatInstallPrompt(installUrl, style) {
60
+ if (style === "short") {
61
+ return `Next: install the ZIVIS GitHub App — ${installUrl}`;
62
+ }
63
+ return [
64
+ "",
65
+ "Next: install the ZIVIS GitHub App",
66
+ ` ${installUrl}`,
67
+ " Enables continuous monitoring + automatic threat-model updates on push.",
68
+ ].join("\n");
69
+ }
70
+ function formatLinkRepoPrompt(config, applicationId, style) {
71
+ const appBase = process.env.ZIVIS_APP_URL || guessAppUrlFromApi(config.apiBaseUrl);
72
+ const url = `${appBase}/applications/${applicationId}`;
73
+ if (style === "short") {
74
+ return `Next: link a GitHub repo to this application — ${url}`;
75
+ }
76
+ return [
77
+ "",
78
+ "Next: link a GitHub repo to this application",
79
+ ` ${url}`,
80
+ " The GitHub App is installed but this app has no linked repo yet.",
81
+ ].join("\n");
82
+ }
83
+ function guessAppUrlFromApi(apiBaseUrl) {
84
+ try {
85
+ const u = new URL(apiBaseUrl);
86
+ if (u.host.startsWith("api.")) {
87
+ u.host = "app." + u.host.slice(4);
88
+ return `${u.protocol}//${u.host}`;
89
+ }
90
+ return apiBaseUrl;
91
+ }
92
+ catch {
93
+ return "https://app.zivis.ai";
94
+ }
95
+ }
@@ -1,7 +1,17 @@
1
- export declare function setupCursor(session?: string): Promise<void>;
2
- export declare function setupVsCodeCopilot(session?: string): Promise<void>;
3
- export declare function setupVsCodeClaude(session?: string): Promise<void>;
4
- export declare function setupClaudeCodeCli(session?: string): Promise<void>;
5
- export declare function setupCline(session?: string): Promise<void>;
6
- export declare function setupWindsurf(session?: string): Promise<void>;
7
- export declare function printClaudeCodeManual(session?: string): void;
1
+ export type Package = "appsec" | "builder";
2
+ export type GuidanceClient = "cursor" | "claude-code-cli" | "vscode-copilot" | "vscode-claude" | "cline" | "windsurf";
3
+ export interface GuidanceOptions {
4
+ cwd: string;
5
+ client: GuidanceClient;
6
+ }
7
+ export declare function writeGuidance(opts: GuidanceOptions): string[];
8
+ export declare function removeGuidance(opts: GuidanceOptions): string[];
9
+ export declare function detectIdesInRepo(cwd: string): GuidanceClient[];
10
+ export declare function setupCursor(pkg?: Package): Promise<void>;
11
+ export declare function setupCursorWorkspace(pkg?: Package): Promise<void>;
12
+ export declare function setupVsCodeCopilot(pkg?: Package): Promise<void>;
13
+ export declare function setupVsCodeClaude(pkg?: Package): Promise<void>;
14
+ export declare function setupClaudeCodeCli(pkg?: Package): Promise<void>;
15
+ export declare function setupCline(pkg?: Package): Promise<void>;
16
+ export declare function setupWindsurf(pkg?: Package): Promise<void>;
17
+ export declare function printClaudeCodeManual(pkg?: Package): void;