gog-safe 0.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.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # gog-safe
2
+
3
+ `gog-safe` is a policy-enforced wrapper around `gog`.
4
+ It blocks disallowed Gmail/Sheets operations, enforces JSON/non-interactive execution, and can run command-specific post-validation.
5
+
6
+ ## Safety model
7
+
8
+ - `gog-safe` always returns a unified JSON response for `run`.
9
+ - Validator failures (timeout, non-JSON output, non-zero exit, contract violation) are fail-closed: `safety.action="block"`, `ok=false`.
10
+ - Audit log write failure is fail-open by default (`logging.require_audit_success=false`), and adds `audit_log_write_failed`.
11
+ - If `logging.require_audit_success=true`, audit failure is fail-closed.
12
+ - Final safety status must be read from `safety.action` and `ok`.
13
+ - `allowed` means only allow/deny command decision and does not include validator result.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install
19
+ npm run build
20
+ ```
21
+
22
+ ## CLI
23
+
24
+ ```bash
25
+ gog-safe run --policy ./policy.yml -- sheets get --range A1:B2
26
+ gog-safe validate-policy --policy ./policy.yml
27
+ gog-safe print-effective-policy --policy ./policy.yml
28
+ gog-safe version
29
+ ```
30
+
31
+ ## Config files and precedence
32
+
33
+ `gog-safe` merges files in this order:
34
+
35
+ 1. `policy.yml` (required)
36
+ 2. `~/.gog-safe/config.json` (optional)
37
+ 3. First `.gog-safe/config.json` found while walking from current directory to parent directories (optional)
38
+
39
+ Home config is loaded before project config. Project config wins where override applies.
40
+
41
+ ## Merge rules
42
+
43
+ - `commands.allow`: override priority (`project > home > policy`)
44
+ - `commands.deny`: union (`policy ∪ home ∪ project`)
45
+ - `validation.rules`: concatenation (`policy + home + project`)
46
+ - `validation.validators`: key merge (`project > home > policy`)
47
+ - `gog`, `execution`, `logging`: key override (`project > home > policy`)
48
+
49
+ ## Run behavior
50
+
51
+ `run` enforces flags:
52
+
53
+ - `--json` (when `execution.enforce_json=true`)
54
+ - `--no-input` (when `execution.enforce_no_input=true`)
55
+ - `--account=<gog.account>`
56
+ - `--client=<gog.client>` only when `gog.client` is non-empty
57
+
58
+ User-provided `--json`, `--no-input`, `--account`, `--client` are rejected (including `--k=v` style).
59
+
60
+ `validation.rules.match` is evaluated against the original user argv (before forced flags).
61
+
62
+ v1 intentionally does not validate command option semantics.
63
+
64
+ ## Validator contract (`external_command`)
65
+
66
+ stdin JSON:
67
+
68
+ ```json
69
+ {
70
+ "command": "gog sheets get --range A1:B2",
71
+ "argv": ["sheets", "get", "--range", "A1:B2"],
72
+ "output": {"...": "..."},
73
+ "policy_version": 1
74
+ }
75
+ ```
76
+
77
+ stdout JSON:
78
+
79
+ ```json
80
+ {
81
+ "decision": "allow",
82
+ "reason": "safe"
83
+ }
84
+ ```
85
+
86
+ Rules:
87
+
88
+ - timeout / non-JSON / non-zero exit / invalid `decision` or missing `reason` => `block`
89
+ - multiple validators are OR-block
90
+ - when multiple rules match, validators are merged by rule order, deduplicated, and executed in first-seen order
91
+
92
+ ## Output schema (`run`)
93
+
94
+ ```json
95
+ {
96
+ "ok": true,
97
+ "allowed": true,
98
+ "command": "gog sheets get",
99
+ "safety": {
100
+ "action": "allow",
101
+ "reasons": []
102
+ },
103
+ "data": {},
104
+ "meta": {
105
+ "policy_version": 1
106
+ }
107
+ }
108
+ ```
109
+
110
+ ## Exit codes (`run`)
111
+
112
+ - `0`: final allow (`ok=true`)
113
+ - `2`: policy/validator block (including fail-closed validator or audit when configured)
114
+ - `3`: runtime/system error (gog spawn/timeout/non-zero, invalid gog JSON, internal errors)
115
+
116
+ ## Operations examples
117
+
118
+ Project-level deny override:
119
+
120
+ ```json
121
+ {
122
+ "commands": {
123
+ "deny": ["gmail labels *"]
124
+ }
125
+ }
126
+ ```
127
+
128
+ Project-level allow override:
129
+
130
+ ```json
131
+ {
132
+ "commands": {
133
+ "allow": ["sheets get"]
134
+ }
135
+ }
136
+ ```
137
+
138
+ Add validator only for `sheets get`:
139
+
140
+ ```json
141
+ {
142
+ "validation": {
143
+ "rules": [
144
+ { "match": "sheets get", "validators": ["llm-guard"] }
145
+ ]
146
+ }
147
+ }
148
+ ```
@@ -0,0 +1,10 @@
1
+ export interface AuditRecord {
2
+ timestamp: string;
3
+ command: string;
4
+ allowed: boolean;
5
+ ok: boolean;
6
+ action: "allow" | "block";
7
+ reasons: string[];
8
+ metadata?: Record<string, unknown>;
9
+ }
10
+ export declare function appendAuditLog(auditFile: string, record: AuditRecord): Promise<void>;
package/dist/audit.js ADDED
@@ -0,0 +1,9 @@
1
+ import { appendFile, mkdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export async function appendAuditLog(auditFile, record) {
4
+ const resolved = path.resolve(process.cwd(), auditFile);
5
+ const dir = path.dirname(resolved);
6
+ await mkdir(dir, { recursive: true });
7
+ await appendFile(resolved, `${JSON.stringify(record)}\n`, "utf8");
8
+ }
9
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,IAAI,MAAM,WAAW,CAAC;AAY7B,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,SAAiB,EAAE,MAAmB;IACzE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,UAAU,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACpE,CAAC"}
@@ -0,0 +1,12 @@
1
+ export interface CommandDecision {
2
+ allowed: boolean;
3
+ reasons: string[];
4
+ }
5
+ export declare function decideCommandAllowance(positionals: string[], allowPatterns: string[], denyPatterns: string[]): CommandDecision;
6
+ export declare function findForbiddenOverride(argv: string[]): string | undefined;
7
+ export declare function buildForcedArgs(argv: string[], options: {
8
+ enforceJson: boolean;
9
+ enforceNoInput: boolean;
10
+ account: string;
11
+ client: string;
12
+ }): string[];
@@ -0,0 +1,49 @@
1
+ import { matchCommandPattern } from "./matching.js";
2
+ export function decideCommandAllowance(positionals, allowPatterns, denyPatterns) {
3
+ for (const denyPattern of denyPatterns) {
4
+ if (matchCommandPattern(denyPattern, positionals)) {
5
+ return {
6
+ allowed: false,
7
+ reasons: [`command_denied:${denyPattern}`],
8
+ };
9
+ }
10
+ }
11
+ for (const allowPattern of allowPatterns) {
12
+ if (matchCommandPattern(allowPattern, positionals)) {
13
+ return {
14
+ allowed: true,
15
+ reasons: [`command_allowed:${allowPattern}`],
16
+ };
17
+ }
18
+ }
19
+ return {
20
+ allowed: false,
21
+ reasons: ["command_not_allowed"],
22
+ };
23
+ }
24
+ const FORBIDDEN_OVERRIDE_OPTIONS = ["--json", "--no-input", "--account", "--client"];
25
+ export function findForbiddenOverride(argv) {
26
+ for (const token of argv) {
27
+ for (const forbidden of FORBIDDEN_OVERRIDE_OPTIONS) {
28
+ if (token === forbidden || token.startsWith(`${forbidden}=`)) {
29
+ return forbidden;
30
+ }
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+ export function buildForcedArgs(argv, options) {
36
+ const forced = [...argv];
37
+ if (options.enforceJson) {
38
+ forced.push("--json");
39
+ }
40
+ if (options.enforceNoInput) {
41
+ forced.push("--no-input");
42
+ }
43
+ forced.push(`--account=${options.account}`);
44
+ if (options.client.trim().length > 0) {
45
+ forced.push(`--client=${options.client}`);
46
+ }
47
+ return forced;
48
+ }
49
+ //# sourceMappingURL=command-control.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-control.js","sourceRoot":"","sources":["../src/command-control.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAOpD,MAAM,UAAU,sBAAsB,CACpC,WAAqB,EACrB,aAAuB,EACvB,YAAsB;IAEtB,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,IAAI,mBAAmB,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;YAClD,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,CAAC,kBAAkB,WAAW,EAAE,CAAC;aAC3C,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;QACzC,IAAI,mBAAmB,CAAC,YAAY,EAAE,WAAW,CAAC,EAAE,CAAC;YACnD,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,mBAAmB,YAAY,EAAE,CAAC;aAC7C,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,CAAC,qBAAqB,CAAC;KACjC,CAAC;AACJ,CAAC;AAED,MAAM,0BAA0B,GAAG,CAAC,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;AAErF,MAAM,UAAU,qBAAqB,CAAC,IAAc;IAClD,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,KAAK,MAAM,SAAS,IAAI,0BAA0B,EAAE,CAAC;YACnD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC;gBAC7D,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,IAAc,EACd,OAKC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAEzB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,aAAa,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAE5C,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare class UserInputError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class PolicyValidationError extends Error {
5
+ readonly issues: string[];
6
+ constructor(issues: string[]);
7
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,15 @@
1
+ export class UserInputError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "UserInputError";
5
+ }
6
+ }
7
+ export class PolicyValidationError extends Error {
8
+ issues;
9
+ constructor(issues) {
10
+ super(`Policy validation failed (${issues.length} issue(s))`);
11
+ this.name = "PolicyValidationError";
12
+ this.issues = issues;
13
+ }
14
+ }
15
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACrC,MAAM,CAAW;IAE1B,YAAY,MAAgB;QAC1B,KAAK,CAAC,6BAA6B,MAAM,CAAC,MAAM,YAAY,CAAC,CAAC;QAC9D,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { PolicyValidationError, UserInputError } from "./errors.js";
6
+ import { loadEffectivePolicy } from "./policy.js";
7
+ import { executeRun } from "./run.js";
8
+ function usage() {
9
+ return [
10
+ "Usage:",
11
+ " gog-safe run --policy <path> -- <gog args...>",
12
+ " gog-safe validate-policy --policy <path>",
13
+ " gog-safe print-effective-policy --policy <path>",
14
+ " gog-safe version",
15
+ ].join("\n");
16
+ }
17
+ function parsePolicyOption(argv) {
18
+ for (let i = 0; i < argv.length; i += 1) {
19
+ if (argv[i] === "--policy") {
20
+ const value = argv[i + 1];
21
+ if (!value || value.startsWith("--")) {
22
+ throw new UserInputError("--policy requires a value");
23
+ }
24
+ return value;
25
+ }
26
+ if (argv[i].startsWith("--policy=")) {
27
+ const value = argv[i].slice("--policy=".length);
28
+ if (!value) {
29
+ throw new UserInputError("--policy requires a value");
30
+ }
31
+ return value;
32
+ }
33
+ }
34
+ throw new UserInputError("--policy is required");
35
+ }
36
+ function parseRunArgs(argv) {
37
+ const separator = argv.indexOf("--");
38
+ const beforeSeparator = separator >= 0 ? argv.slice(0, separator) : argv;
39
+ const gogArgs = separator >= 0 ? argv.slice(separator + 1) : [];
40
+ const policyPath = parsePolicyOption(beforeSeparator);
41
+ if (gogArgs.length === 0) {
42
+ throw new UserInputError("run requires gog args after '--'");
43
+ }
44
+ return { policyPath, gogArgs };
45
+ }
46
+ async function readPackageVersion() {
47
+ const currentFile = fileURLToPath(import.meta.url);
48
+ const currentDir = path.dirname(currentFile);
49
+ const packagePath = path.resolve(currentDir, "..", "package.json");
50
+ const text = await readFile(packagePath, "utf8");
51
+ const pkg = JSON.parse(text);
52
+ return pkg.version ?? "0.0.0";
53
+ }
54
+ function printPolicyError(error) {
55
+ for (const issue of error.issues) {
56
+ console.error(`- ${issue}`);
57
+ }
58
+ }
59
+ async function main() {
60
+ const argv = process.argv.slice(2);
61
+ const command = argv[0];
62
+ if (!command) {
63
+ console.error(usage());
64
+ return 1;
65
+ }
66
+ try {
67
+ if (command === "version") {
68
+ console.log(await readPackageVersion());
69
+ return 0;
70
+ }
71
+ if (command === "validate-policy") {
72
+ const policyPath = parsePolicyOption(argv.slice(1));
73
+ await loadEffectivePolicy(policyPath);
74
+ console.log("policy is valid");
75
+ return 0;
76
+ }
77
+ if (command === "print-effective-policy") {
78
+ const policyPath = parsePolicyOption(argv.slice(1));
79
+ const loaded = await loadEffectivePolicy(policyPath);
80
+ console.log(JSON.stringify(loaded.effective, null, 2));
81
+ return 0;
82
+ }
83
+ if (command === "run") {
84
+ const { policyPath, gogArgs } = parseRunArgs(argv.slice(1));
85
+ const result = await executeRun(policyPath, gogArgs);
86
+ console.log(JSON.stringify(result.output));
87
+ for (const warning of result.stderrWarnings) {
88
+ if (warning.trim().length > 0) {
89
+ console.error(warning);
90
+ }
91
+ }
92
+ return result.exitCode;
93
+ }
94
+ console.error(`unknown command: ${command}`);
95
+ console.error(usage());
96
+ return 1;
97
+ }
98
+ catch (error) {
99
+ if (error instanceof PolicyValidationError) {
100
+ printPolicyError(error);
101
+ return 1;
102
+ }
103
+ if (error instanceof UserInputError) {
104
+ console.error(error.message);
105
+ console.error(usage());
106
+ return 1;
107
+ }
108
+ const message = error instanceof Error ? error.message : String(error);
109
+ console.error(message);
110
+ return 1;
111
+ }
112
+ }
113
+ main()
114
+ .then((code) => {
115
+ process.exitCode = code;
116
+ })
117
+ .catch((error) => {
118
+ console.error(error instanceof Error ? error.stack : String(error));
119
+ process.exitCode = 1;
120
+ });
121
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,SAAS,KAAK;IACZ,OAAO;QACL,QAAQ;QACR,iDAAiD;QACjD,4CAA4C;QAC5C,mDAAmD;QACnD,oBAAoB;KACrB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAc;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrC,MAAM,IAAI,cAAc,CAAC,2BAA2B,CAAC,CAAC;YACxD,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAChD,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,cAAc,CAAC,2BAA2B,CAAC,CAAC;YACxD,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,MAAM,IAAI,cAAc,CAAC,sBAAsB,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,YAAY,CAAC,IAAc;IAClC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,eAAe,GAAG,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACzE,MAAM,OAAO,GAAG,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAEhE,MAAM,UAAU,GAAG,iBAAiB,CAAC,eAAe,CAAC,CAAC;IAEtD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,cAAc,CAAC,kCAAkC,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AACjC,CAAC;AAED,KAAK,UAAU,kBAAkB;IAC/B,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAyB,CAAC;IACrD,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;AAChC,CAAC;AAED,SAAS,gBAAgB,CAAC,KAA4B;IACpD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAExB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QACvB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,CAAC;QACH,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,kBAAkB,EAAE,CAAC,CAAC;YACxC,OAAO,CAAC,CAAC;QACX,CAAC;QAED,IAAI,OAAO,KAAK,iBAAiB,EAAE,CAAC;YAClC,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACpD,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAC;YACtC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;YAC/B,OAAO,CAAC,CAAC;QACX,CAAC;QAED,IAAI,OAAO,KAAK,wBAAwB,EAAE,CAAC;YACzC,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACvD,OAAO,CAAC,CAAC;QACX,CAAC;QAED,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;YACtB,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5D,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAC3C,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;gBAC5C,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;YACD,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QACvB,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,qBAAqB,EAAE,CAAC;YAC3C,gBAAgB,CAAC,KAAK,CAAC,CAAC;YACxB,OAAO,CAAC,CAAC;QACX,CAAC;QAED,IAAI,KAAK,YAAY,cAAc,EAAE,CAAC;YACpC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7B,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YACvB,OAAO,CAAC,CAAC;QACX,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACvB,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,IAAI,EAAE;KACH,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;IACb,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACf,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACpE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { ArgvAnalysis, ValidationRule } from "./types.js";
2
+ export declare function tokenizePattern(pattern: string): string[];
3
+ export declare function validateCommandPattern(pattern: string): string[];
4
+ export declare function parseArgv(argv: string[]): ArgvAnalysis;
5
+ export declare function matchCommandPattern(pattern: string, positionals: string[]): boolean;
6
+ export declare function parseRuleMatch(match: string): {
7
+ positionals: string[];
8
+ requiredOptions: string[];
9
+ };
10
+ export declare function doesRuleMatch(rule: ValidationRule, analyzed: ArgvAnalysis): boolean;
11
+ export declare function resolveValidatorsForCommand(rules: ValidationRule[], analyzed: ArgvAnalysis): string[];
@@ -0,0 +1,118 @@
1
+ export function tokenizePattern(pattern) {
2
+ return pattern
3
+ .trim()
4
+ .split(/\s+/)
5
+ .filter((token) => token.length > 0);
6
+ }
7
+ export function validateCommandPattern(pattern) {
8
+ const issues = [];
9
+ const tokens = tokenizePattern(pattern);
10
+ if (tokens.length === 0) {
11
+ issues.push("pattern must not be empty");
12
+ return issues;
13
+ }
14
+ let starIndex = -1;
15
+ tokens.forEach((token, index) => {
16
+ if (token.includes("*") && token !== "*") {
17
+ issues.push(`invalid wildcard token: ${token}`);
18
+ return;
19
+ }
20
+ if (token === "*") {
21
+ if (starIndex >= 0) {
22
+ issues.push("wildcard '*' must appear at most once");
23
+ }
24
+ starIndex = index;
25
+ }
26
+ });
27
+ if (starIndex >= 0 && starIndex !== tokens.length - 1) {
28
+ issues.push("wildcard '*' must appear only at the end");
29
+ }
30
+ return issues;
31
+ }
32
+ export function parseArgv(argv) {
33
+ const positionals = [];
34
+ const options = new Set();
35
+ for (let i = 0; i < argv.length; i += 1) {
36
+ const token = argv[i];
37
+ if (!token.startsWith("--")) {
38
+ positionals.push(token);
39
+ continue;
40
+ }
41
+ const eqIndex = token.indexOf("=");
42
+ if (eqIndex > 0) {
43
+ options.add(token.slice(0, eqIndex));
44
+ continue;
45
+ }
46
+ options.add(token);
47
+ const next = argv[i + 1];
48
+ // v1 minimal parser:
49
+ // `--k v` is treated as option presence and `v` is not positional.
50
+ // Keep consuming unless the next token is another long option.
51
+ if (next && !next.startsWith("--")) {
52
+ i += 1;
53
+ }
54
+ }
55
+ return { positionals, options };
56
+ }
57
+ export function matchCommandPattern(pattern, positionals) {
58
+ const tokens = tokenizePattern(pattern);
59
+ if (tokens.length === 0) {
60
+ return false;
61
+ }
62
+ const hasWildcard = tokens[tokens.length - 1] === "*";
63
+ const base = hasWildcard ? tokens.slice(0, -1) : tokens;
64
+ if (hasWildcard) {
65
+ if (positionals.length < base.length) {
66
+ return false;
67
+ }
68
+ return base.every((token, index) => token === positionals[index]);
69
+ }
70
+ if (base.length !== positionals.length) {
71
+ return false;
72
+ }
73
+ return base.every((token, index) => token === positionals[index]);
74
+ }
75
+ export function parseRuleMatch(match) {
76
+ const tokens = tokenizePattern(match);
77
+ const positionals = [];
78
+ const requiredOptions = [];
79
+ for (const token of tokens) {
80
+ if (!token.startsWith("--")) {
81
+ positionals.push(token);
82
+ continue;
83
+ }
84
+ const eqIndex = token.indexOf("=");
85
+ requiredOptions.push(eqIndex > 0 ? token.slice(0, eqIndex) : token);
86
+ }
87
+ return { positionals, requiredOptions };
88
+ }
89
+ export function doesRuleMatch(rule, analyzed) {
90
+ const parsed = parseRuleMatch(rule.match);
91
+ if (parsed.positionals.length !== analyzed.positionals.length) {
92
+ return false;
93
+ }
94
+ for (let i = 0; i < parsed.positionals.length; i += 1) {
95
+ if (parsed.positionals[i] !== analyzed.positionals[i]) {
96
+ return false;
97
+ }
98
+ }
99
+ return parsed.requiredOptions.every((option) => analyzed.options.has(option));
100
+ }
101
+ export function resolveValidatorsForCommand(rules, analyzed) {
102
+ const merged = [];
103
+ const seen = new Set();
104
+ for (const rule of rules) {
105
+ if (!doesRuleMatch(rule, analyzed)) {
106
+ continue;
107
+ }
108
+ for (const validatorName of rule.validators) {
109
+ if (seen.has(validatorName)) {
110
+ continue;
111
+ }
112
+ seen.add(validatorName);
113
+ merged.push(validatorName);
114
+ }
115
+ }
116
+ return merged;
117
+ }
118
+ //# sourceMappingURL=matching.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matching.js","sourceRoot":"","sources":["../src/matching.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { EffectivePolicy, LoadedConfigSet, PartialConfig } from "./types.js";
2
+ export declare function buildEffectivePolicy(policyConfig: PartialConfig, homeConfig?: PartialConfig, projectConfig?: PartialConfig): EffectivePolicy;
3
+ export declare function validateEffectivePolicy(policy: EffectivePolicy): string[];
4
+ export declare function loadEffectivePolicy(policyPath: string, cwd?: string): Promise<LoadedConfigSet>;