@zivis/cli 0.1.0-alpha.40 → 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 (66) hide show
  1. package/dist/commands/app/index.js +4 -2
  2. package/dist/commands/assurance/index.d.ts +33 -0
  3. package/dist/commands/assurance/index.js +149 -0
  4. package/dist/commands/auth/index.js +1 -0
  5. package/dist/commands/auth/init.d.ts +1 -0
  6. package/dist/commands/auth/init.js +12 -8
  7. package/dist/commands/gate/index.d.ts +2 -0
  8. package/dist/commands/gate/index.js +171 -0
  9. package/dist/commands/mcp/index.js +33 -1
  10. package/dist/commands/run/index.d.ts +2 -0
  11. package/dist/commands/run/index.js +111 -0
  12. package/dist/commands/sync/index.d.ts +2 -0
  13. package/dist/commands/sync/index.js +187 -0
  14. package/dist/commands/test/index.d.ts +2 -0
  15. package/dist/commands/test/index.js +115 -0
  16. package/dist/commands/tm/index.js +4 -0
  17. package/dist/index.js +11 -0
  18. package/dist/internal/application-binding.d.ts +8 -0
  19. package/dist/internal/application-binding.js +167 -0
  20. package/dist/internal/cli-output.d.ts +16 -0
  21. package/dist/internal/cli-output.js +39 -0
  22. package/dist/internal/devx-run.d.ts +47 -0
  23. package/dist/internal/devx-run.js +36 -0
  24. package/dist/internal/gate-evaluate.d.ts +50 -0
  25. package/dist/internal/gate-evaluate.js +111 -0
  26. package/dist/internal/gate-policy.d.ts +38 -0
  27. package/dist/internal/gate-policy.js +167 -0
  28. package/dist/internal/git-metadata.d.ts +8 -0
  29. package/dist/internal/git-metadata.js +38 -0
  30. package/dist/internal/git.d.ts +8 -0
  31. package/dist/internal/git.js +30 -0
  32. package/dist/internal/ide-setup.d.ts +1 -0
  33. package/dist/internal/ide-setup.js +76 -19
  34. package/dist/internal/inventory-sync.d.ts +74 -0
  35. package/dist/internal/inventory-sync.js +189 -0
  36. package/dist/internal/packs/cache.d.ts +8 -0
  37. package/dist/internal/packs/cache.js +60 -0
  38. package/dist/internal/packs/index.d.ts +10 -0
  39. package/dist/internal/packs/index.js +7 -0
  40. package/dist/internal/packs/integrity.d.ts +9 -0
  41. package/dist/internal/packs/integrity.js +24 -0
  42. package/dist/internal/packs/jcs.d.ts +2 -0
  43. package/dist/internal/packs/jcs.js +57 -0
  44. package/dist/internal/packs/local-paths.d.ts +4 -0
  45. package/dist/internal/packs/local-paths.js +26 -0
  46. package/dist/internal/packs/registry-client.d.ts +20 -0
  47. package/dist/internal/packs/registry-client.js +40 -0
  48. package/dist/internal/packs/resolve.d.ts +8 -0
  49. package/dist/internal/packs/resolve.js +89 -0
  50. package/dist/internal/packs/semver.d.ts +9 -0
  51. package/dist/internal/packs/semver.js +57 -0
  52. package/dist/internal/packs/signing.d.ts +5 -0
  53. package/dist/internal/packs/signing.js +49 -0
  54. package/dist/internal/packs/types.d.ts +70 -0
  55. package/dist/internal/packs/types.js +20 -0
  56. package/dist/internal/run-workdir.d.ts +1 -0
  57. package/dist/internal/run-workdir.js +11 -0
  58. package/dist/internal/security-context.d.ts +62 -0
  59. package/dist/internal/security-context.js +50 -0
  60. package/dist/internal/sync-outbox.d.ts +40 -0
  61. package/dist/internal/sync-outbox.js +66 -0
  62. package/dist/internal/test-scope.d.ts +68 -0
  63. package/dist/internal/test-scope.js +69 -0
  64. package/dist/internal/zivis-local-state.d.ts +1 -0
  65. package/dist/internal/zivis-local-state.js +22 -0
  66. package/package.json +3 -2
@@ -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
+ }
@@ -5,6 +5,7 @@ export interface GuidanceOptions {
5
5
  client: GuidanceClient;
6
6
  }
7
7
  export declare function writeGuidance(opts: GuidanceOptions): string[];
8
+ export declare function removeGuidance(opts: GuidanceOptions): string[];
8
9
  export declare function detectIdesInRepo(cwd: string): GuidanceClient[];
9
10
  export declare function setupCursor(pkg?: Package): Promise<void>;
10
11
  export declare function setupCursorWorkspace(pkg?: Package): Promise<void>;
@@ -27,6 +27,19 @@ function appendBlock(filePath, blockKey, content) {
27
27
  function escapeRegex(s) {
28
28
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
29
29
  }
30
+ function removeBlockFrom(filePath, blockKey) {
31
+ if (!fs.existsSync(filePath))
32
+ return false;
33
+ const begin = `<!-- BEGIN ZIVIS:${blockKey} -->`;
34
+ const end = `<!-- END ZIVIS:${blockKey} -->`;
35
+ const blockRegex = new RegExp(`\\n?${escapeRegex(begin)}[\\s\\S]*?${escapeRegex(end)}\\n?`, "g");
36
+ const existing = fs.readFileSync(filePath, "utf-8");
37
+ if (!blockRegex.test(existing))
38
+ return false;
39
+ const stripped = existing.replace(blockRegex, "\n").replace(/\n{3,}/g, "\n\n");
40
+ fs.writeFileSync(filePath, stripped);
41
+ return true;
42
+ }
30
43
  function cursorRuleContent() {
31
44
  return `---
32
45
  description: ZIVIS security guidance — quietly suggest security checks at the right moments
@@ -112,21 +125,14 @@ might break in", "security plan", "categories of attacks".
112
125
  <!-- END ZIVIS:guidance -->`;
113
126
  }
114
127
  function claudeBlockContent() {
115
- return `## ZIVIS Security (MCP)
116
-
117
- ZIVIS is connected to this project. Use its tools sparingly — only for security-related topics.
128
+ return `## ZIVIS
118
129
 
119
- **When to use ZIVIS:**
120
- - User asks "is this secure" / "check for vulnerabilities" / "audit my deps" → call \`zivis_get_started\` first
121
- - User adds a new npm/pip/go package → offer to call \`zivis_check_repo_trust\` (no auth needed)
122
- - User adds openai/anthropic/langchain import → offer to call \`zivis_setup_red_team_target\` (target_type=ai_chat)
123
- - User mentions "launch", "audit", "compliance", "pen test" → call \`zivis_get_started\` with concern=<their phrase>
130
+ ZIVIS is connected to this project via the \`zivis\` CLI — one agent-neutral command surface, not IDE-specific tooling.
124
131
 
125
- **When NOT to use:** refactors, performance, styling, tests, business logic, anything non-security.
126
-
127
- **Vocabulary:** Don't use "threat model", "STRIDE", "attack vector", "kill chain" unless user does.
128
-
129
- **Slash commands:** /zivis-getting-started /zivis-secure-my-app /zivis-audit-dependencies /zivis-prep-for-launch`;
132
+ - Asked to security test or review this repo run \`zivis test\` (or \`zivis test <scope>\` for a focused pass; \`zivis test --list\` shows available scopes) and follow the guidance it returns.
133
+ - Asked to threat model this repo → run \`zivis threatmodel\` and follow the guidance it returns.
134
+ - Use your own repo understanding, reasoning, and shell/tools/browser access as needed — these commands are a starting point, not a substitute for judgment.
135
+ - Do not treat their output alone as a confirmed finding; verify before reporting.`;
130
136
  }
131
137
  function ruleFileContent() {
132
138
  return `# ZIVIS Security Guidance
@@ -191,10 +197,8 @@ function writeClaudeBlockGuidance(cwd) {
191
197
  appendBlock(claudePath, "zivis-security", claudeBlockContent());
192
198
  written.push(claudePath);
193
199
  const agentsPath = path.join(cwd, "AGENTS.md");
194
- if (fs.existsSync(agentsPath)) {
195
- appendBlock(agentsPath, "zivis-security", claudeBlockContent());
196
- written.push(agentsPath);
197
- }
200
+ appendBlock(agentsPath, "zivis-security", claudeBlockContent());
201
+ written.push(agentsPath);
198
202
  return written;
199
203
  }
200
204
  function writeCopilotGuidance(cwd) {
@@ -221,8 +225,14 @@ export function writeGuidance(opts) {
221
225
  switch (client) {
222
226
  case "cursor": {
223
227
  written.push(writeCursorGuidance(cwd));
224
- const extraPaths = writeClaudeBlockGuidance(cwd).filter((p) => fs.existsSync(p));
225
- written.push(...extraPaths);
228
+ const claudePath = path.join(cwd, "CLAUDE.md");
229
+ appendBlock(claudePath, "zivis-security", claudeBlockContent());
230
+ written.push(claudePath);
231
+ const agentsPath = path.join(cwd, "AGENTS.md");
232
+ if (fs.existsSync(agentsPath)) {
233
+ appendBlock(agentsPath, "zivis-security", claudeBlockContent());
234
+ written.push(agentsPath);
235
+ }
226
236
  break;
227
237
  }
228
238
  case "claude-code-cli":
@@ -245,6 +255,53 @@ export function writeGuidance(opts) {
245
255
  }
246
256
  return written.filter(Boolean);
247
257
  }
258
+ export function removeGuidance(opts) {
259
+ const { cwd, client } = opts;
260
+ const changed = [];
261
+ const unlinkIfExists = (p) => {
262
+ if (fs.existsSync(p)) {
263
+ fs.unlinkSync(p);
264
+ changed.push(p);
265
+ }
266
+ };
267
+ switch (client) {
268
+ case "cursor": {
269
+ unlinkIfExists(path.join(cwd, ".cursor", "rules", "zivis.mdc"));
270
+ if (removeBlockFrom(path.join(cwd, "CLAUDE.md"), "zivis-security")) {
271
+ changed.push(path.join(cwd, "CLAUDE.md"));
272
+ }
273
+ if (removeBlockFrom(path.join(cwd, "AGENTS.md"), "zivis-security")) {
274
+ changed.push(path.join(cwd, "AGENTS.md"));
275
+ }
276
+ break;
277
+ }
278
+ case "claude-code-cli":
279
+ case "vscode-claude": {
280
+ if (removeBlockFrom(path.join(cwd, "CLAUDE.md"), "zivis-security")) {
281
+ changed.push(path.join(cwd, "CLAUDE.md"));
282
+ }
283
+ if (removeBlockFrom(path.join(cwd, "AGENTS.md"), "zivis-security")) {
284
+ changed.push(path.join(cwd, "AGENTS.md"));
285
+ }
286
+ break;
287
+ }
288
+ case "vscode-copilot": {
289
+ if (removeBlockFrom(path.join(cwd, ".github", "copilot-instructions.md"), "zivis-security")) {
290
+ changed.push(path.join(cwd, ".github", "copilot-instructions.md"));
291
+ }
292
+ break;
293
+ }
294
+ case "cline": {
295
+ unlinkIfExists(path.join(cwd, ".clinerules"));
296
+ break;
297
+ }
298
+ case "windsurf": {
299
+ unlinkIfExists(path.join(cwd, ".windsurfrules"));
300
+ break;
301
+ }
302
+ }
303
+ return changed;
304
+ }
248
305
  export function detectIdesInRepo(cwd) {
249
306
  const found = [];
250
307
  if (fs.existsSync(path.join(cwd, ".cursor"))) {
@@ -0,0 +1,74 @@
1
+ export interface InventorySyncEndpoint {
2
+ path: string;
3
+ method: string;
4
+ summary?: string;
5
+ description?: string;
6
+ authRequired?: boolean;
7
+ tags?: string[];
8
+ deprecated?: boolean;
9
+ }
10
+ export interface InventorySyncFeature {
11
+ name: string;
12
+ description?: string;
13
+ criticality?: "critical" | "high" | "medium" | "low";
14
+ endpoints?: string[];
15
+ tags?: string[];
16
+ }
17
+ export interface InventorySyncInput {
18
+ endpoints?: InventorySyncEndpoint[];
19
+ features?: InventorySyncFeature[];
20
+ technologyStack?: string[];
21
+ }
22
+ export interface InventorySyncProvenance {
23
+ gitCommitSha?: string | null;
24
+ repoFullName?: string | null;
25
+ defaultBranch?: string | null;
26
+ source: "cli" | "agent";
27
+ }
28
+ export type ValidationResult = {
29
+ ok: true;
30
+ value: InventorySyncInput;
31
+ } | {
32
+ ok: false;
33
+ errors: string[];
34
+ };
35
+ export declare function validateInventorySyncInput(raw: unknown): ValidationResult;
36
+ export interface EndpointReconcileResult {
37
+ created: number;
38
+ updated: number;
39
+ deprecated: number;
40
+ total: number;
41
+ }
42
+ export interface FeatureReconcileResult {
43
+ created: number;
44
+ updated: number;
45
+ deprecated: number;
46
+ endpointsLinked: number;
47
+ filesLinked: number;
48
+ }
49
+ export interface TechStackResult {
50
+ id: string;
51
+ techStack: string[];
52
+ }
53
+ export interface RepoSyncResult {
54
+ created: boolean;
55
+ repoFullName: string;
56
+ }
57
+ export interface InventorySyncSummary {
58
+ endpoints?: EndpointReconcileResult;
59
+ features?: FeatureReconcileResult;
60
+ technologyStack?: {
61
+ count: number;
62
+ };
63
+ repo?: RepoSyncResult;
64
+ }
65
+ export interface InventorySyncApi {
66
+ put<T>(path: string, body: unknown): Promise<T>;
67
+ post<T>(path: string, body: unknown): Promise<T>;
68
+ }
69
+ export declare function syncApplicationInventory(params: {
70
+ client: InventorySyncApi;
71
+ applicationId: string;
72
+ input: InventorySyncInput;
73
+ provenance: InventorySyncProvenance;
74
+ }): Promise<InventorySyncSummary>;