coderifts 1.9.0 → 3.0.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 +134 -0
- package/bin/coderifts.js +119 -1
- package/dist/cli.js +2873 -103
- package/package.json +5 -3
- package/scripts/assert-publish-gate.sh +42 -0
package/README.md
CHANGED
|
@@ -64,6 +64,140 @@ Interactive configuration generator. Creates a `.coderifts.yml` file with indust
|
|
|
64
64
|
|
|
65
65
|
Save your CodeRifts API key for cloud features. Get a free key at [app.coderifts.com/api/signup](https://app.coderifts.com/api/signup).
|
|
66
66
|
|
|
67
|
+
### `coderifts publish-gate`
|
|
68
|
+
|
|
69
|
+
Gate **npm publish** on the same contract-preflight family as the pre-push hook:
|
|
70
|
+
`before` = last published / merge-base baseline, `after` = working tree. Exit **0**
|
|
71
|
+
only when `execution_action` permits publish; **BLOCK**, resolver errors, empty
|
|
72
|
+
before, and preflight unreachability all exit **1** (fail-closed). Empty `before`
|
|
73
|
+
is **never** treated as a silent NEW_ARTIFACT pass.
|
|
74
|
+
|
|
75
|
+
| Flag | Description | Default |
|
|
76
|
+
|------|-------------|---------|
|
|
77
|
+
| `--spec <path>` | Contract artifact path | `git config coderifts.specPath` or `api/openapi.yaml` |
|
|
78
|
+
| `--json` | Machine-readable result | `false` |
|
|
79
|
+
|
|
80
|
+
**Recommended package.json wiring:**
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"scripts": {
|
|
85
|
+
"prepublishOnly": "coderifts publish-gate"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Before resolution order (fail-closed):**
|
|
91
|
+
1. Git tag of `package.json` version (`vX.Y.Z`, then `X.Y.Z`)
|
|
92
|
+
2. `git merge-base HEAD origin/main` (fallbacks: `origin/master`, `main`, `master`)
|
|
93
|
+
3. Exit 1 with a clear error — never invent an empty baseline
|
|
94
|
+
|
|
95
|
+
On success the command prints a **receipt reference** when the preflight path issues one.
|
|
96
|
+
|
|
97
|
+
### `coderifts registry-gate [dir]`
|
|
98
|
+
|
|
99
|
+
Local **registry admission gate** for a directory of OpenAPI/Swagger specs
|
|
100
|
+
(audit **3.4/9**). Runs the pure registry-validation core offline — **no cloud
|
|
101
|
+
calls** — so a registry CI step does not depend on CodeRifts uptime to admit
|
|
102
|
+
specs.
|
|
103
|
+
|
|
104
|
+
Discovers `*.yaml` / `*.yml` / `*.json` under `[dir]` (default: `.`), skips
|
|
105
|
+
`node_modules` and dotdirs, skips non-spec files (no `openapi`/`swagger` field),
|
|
106
|
+
and fails closed on unreadable files (`GATE_ERROR`) or zero specs
|
|
107
|
+
(`REGISTRY_EMPTY`).
|
|
108
|
+
|
|
109
|
+
| Flag | Description | Default |
|
|
110
|
+
|------|-------------|---------|
|
|
111
|
+
| `[dir]` | Registry root to scan | `.` |
|
|
112
|
+
| `--glob <pattern>` | Filter paths with `@coderifts/agent-guard` `matchGlob` (relative to dir) | all candidates |
|
|
113
|
+
| `--errors-only` | Fail only on **ERROR** severity (warnings printed, exit 0) | off |
|
|
114
|
+
| `--warn-only` | Advisory: print findings, always exit **0** | off |
|
|
115
|
+
|
|
116
|
+
**Severity (important):** core `endpoint_collision` is a **WARNING** and leaves
|
|
117
|
+
`valid: true`. This gate fails on **ERROR + WARNING** by default — it does **not**
|
|
118
|
+
branch on `valid` alone. Info findings are printed and do not fail.
|
|
119
|
+
|
|
120
|
+
**GitHub Actions (minimal):**
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
- uses: actions/checkout@v4
|
|
124
|
+
- name: Registry admission
|
|
125
|
+
run: npx coderifts registry-gate specs/
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Git hook (`coderifts hook install`)
|
|
129
|
+
|
|
130
|
+
Installs a **pre-push** hook that diffs the configured OpenAPI path against a
|
|
131
|
+
resolved baseline (remote tip, or for new branches a merge-base with the
|
|
132
|
+
default branch). Empty-before allow is not used. The hook prefers closed-set
|
|
133
|
+
`execution_action` from the diff API (legacy `omega_decision` only when action
|
|
134
|
+
is absent). Configure with `git config coderifts.apiKey` and optional
|
|
135
|
+
`git config coderifts.specPath`.
|
|
136
|
+
|
|
137
|
+
After upgrading the `coderifts` CLI, re-run `coderifts hook install` so the
|
|
138
|
+
installed hook matches the package (already-installed hooks are not
|
|
139
|
+
auto-updated).
|
|
140
|
+
|
|
141
|
+
### Agent host files (`coderifts agent-setup`)
|
|
142
|
+
|
|
143
|
+
Writes the six CodeRifts agent-host rule files into the current repo (or `--out <dir>`):
|
|
144
|
+
|
|
145
|
+
| File | Host |
|
|
146
|
+
|------|------|
|
|
147
|
+
| `AGENTS.md` | multi-agent / open convention |
|
|
148
|
+
| `CLAUDE.md` | Claude Code |
|
|
149
|
+
| `.cursor/rules/coderifts.mdc` | Cursor |
|
|
150
|
+
| `.github/copilot-instructions.md` | GitHub Copilot |
|
|
151
|
+
| `coderifts-langgraph-policy.js` | LangGraph |
|
|
152
|
+
| `openai-agent-instructions.md` | OpenAI Agents SDK |
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
coderifts agent-setup # write (skip existing files)
|
|
156
|
+
coderifts agent-setup --force # overwrite
|
|
157
|
+
coderifts agent-setup --check # drift vs embedded content
|
|
158
|
+
coderifts agent-setup --out ./docs # custom root
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Content is vendored from the app generator (`scripts/generate-agent-host-files.js`);
|
|
162
|
+
re-sync with `node scripts/sync-cli-agent-host-embed.js` when the rule source changes.
|
|
163
|
+
|
|
164
|
+
Policy template for agent/MCP APIs: `coderifts init ai-agent` (aliases: `agent`, `mcp`).
|
|
165
|
+
|
|
166
|
+
### Required check wizard (`coderifts setup-required-check`)
|
|
167
|
+
|
|
168
|
+
Guided setup so **CodeRifts / contract-gate** is a **required status check** on
|
|
169
|
+
your default branch (audit **3.4/1** + **3.5**; registry admission is **3.4/9**
|
|
170
|
+
via `coderifts registry-gate`). The wizard:
|
|
171
|
+
|
|
172
|
+
1. Resolves `owner/repo` from `git remote origin` (or `--repo`)
|
|
173
|
+
2. Observes classic branch protection via **`gh api`** (your login — never a
|
|
174
|
+
CodeRifts API key)
|
|
175
|
+
3. Detects **repository rulesets** that already require the check (honest
|
|
176
|
+
report; classic GET `/protection` does not include rulesets)
|
|
177
|
+
4. **Default dry-run:** prints the exact `gh api --method PUT … --input -`
|
|
178
|
+
read-modify-write command that adds the check **without clobbering**
|
|
179
|
+
existing protection settings
|
|
180
|
+
5. **`--apply`:** runs that PUT with your credentials, then **re-reads** and
|
|
181
|
+
only reports success if the check is required
|
|
182
|
+
|
|
183
|
+
**Why the App never writes protection:** mutating branch protection needs
|
|
184
|
+
`administration:write` — a trust jump we refuse. Your credentials do the write.
|
|
185
|
+
|
|
186
|
+
**Merge queues:** when this check is required, the GitHub App also handles the
|
|
187
|
+
`merge_group` webhook (`checks_requested`) and posts **CodeRifts / contract-gate**
|
|
188
|
+
on the merge group head (same gate as PR heads), so the queue does not stall.
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Observe + print command (safe default)
|
|
192
|
+
coderifts setup-required-check
|
|
193
|
+
|
|
194
|
+
# Apply + re-verify
|
|
195
|
+
coderifts setup-required-check --apply
|
|
196
|
+
|
|
197
|
+
# One-liner for CI docs
|
|
198
|
+
coderifts setup-required-check --branch main
|
|
199
|
+
```
|
|
200
|
+
|
|
67
201
|
## CI/CD Integration
|
|
68
202
|
|
|
69
203
|
### GitHub Actions
|
package/bin/coderifts.js
CHANGED
|
@@ -42,6 +42,46 @@ program
|
|
|
42
42
|
await runDeployGate(options);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
// ── publish-gate — npm prepublishOnly (same decision family as pre-push, fail-closed before) ──
|
|
46
|
+
// Exit status is set only at this command boundary (library never process.exit).
|
|
47
|
+
// program.parse() does not await async actions; process.exit here makes the gate's
|
|
48
|
+
// fail-closed status authoritative for npm prepublishOnly / child_process callers.
|
|
49
|
+
program
|
|
50
|
+
.command('publish-gate')
|
|
51
|
+
.description('Gate npm publish on contract-artifact preflight (before=git baseline, after=working tree)')
|
|
52
|
+
.option('--spec <path>', 'Contract artifact path (default: git config coderifts.specPath or api/openapi.yaml)')
|
|
53
|
+
.option('--json', 'Machine-readable JSON result')
|
|
54
|
+
.action(async (options) => {
|
|
55
|
+
const { runPublishGate } = require('../src/commands/publish-gate');
|
|
56
|
+
const result = await runPublishGate(options);
|
|
57
|
+
const code = result && typeof result.exitCode === 'number' ? result.exitCode : 1;
|
|
58
|
+
process.exitCode = code;
|
|
59
|
+
process.exit(code);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ── registry-gate — local OpenAPI registry admission (pure core, no cloud) ──
|
|
63
|
+
// Fail-closed discovery + severity selection (WARNING findings fail by default;
|
|
64
|
+
// never gate on core `valid` alone — endpoint_collision is warning).
|
|
65
|
+
// process.exit only at this boundary (library returns exitCode).
|
|
66
|
+
program
|
|
67
|
+
.command('registry-gate [dir]')
|
|
68
|
+
.description('Admit a directory of OpenAPI specs via local registry validation (CI gate, no cloud)')
|
|
69
|
+
.option('--glob <pattern>', 'Filter discovered paths with agent-guard matchGlob (relative to dir)')
|
|
70
|
+
.option('--errors-only', 'Fail only on ERROR severity (warnings are printed, exit 0)')
|
|
71
|
+
.option('--warn-only', 'Advisory: print all findings, always exit 0')
|
|
72
|
+
.action((dir, options) => {
|
|
73
|
+
const { runRegistryGate } = require('../src/commands/registry-gate');
|
|
74
|
+
const result = runRegistryGate({
|
|
75
|
+
dir: dir || '.',
|
|
76
|
+
glob: options.glob,
|
|
77
|
+
errorsOnly: !!options.errorsOnly,
|
|
78
|
+
warnOnly: !!options.warnOnly,
|
|
79
|
+
});
|
|
80
|
+
const code = result && typeof result.exitCode === 'number' ? result.exitCode : 1;
|
|
81
|
+
process.exitCode = code;
|
|
82
|
+
process.exit(code);
|
|
83
|
+
});
|
|
84
|
+
|
|
45
85
|
// ── init command ──
|
|
46
86
|
program
|
|
47
87
|
.command('init [template]')
|
|
@@ -60,6 +100,75 @@ program
|
|
|
60
100
|
await login();
|
|
61
101
|
});
|
|
62
102
|
|
|
103
|
+
// ── setup-required-check — guided wizard for branch protection (audit 3.4/1 + 3.5) ──
|
|
104
|
+
// Observes classic protection + rulesets via `gh` (user credentials). Default dry-run;
|
|
105
|
+
// --apply does read-modify-write + re-verify. App never writes protection.
|
|
106
|
+
program
|
|
107
|
+
.command('setup-required-check')
|
|
108
|
+
.description('Guide setup of required status check "CodeRifts / contract-gate" (uses gh, your credentials)')
|
|
109
|
+
.option('--branch <name>', 'Branch to protect (default: repo default branch)')
|
|
110
|
+
.option('--repo <owner/repo>', 'Override owner/repo (default: git remote origin)')
|
|
111
|
+
.option('--apply', 'Apply the protection change (default: print the exact gh command only)')
|
|
112
|
+
.option('--json', 'Machine-readable JSON result')
|
|
113
|
+
.action(async (options) => {
|
|
114
|
+
const { runSetupRequiredCheck } = require('../src/commands/setup-required-check');
|
|
115
|
+
const result = await runSetupRequiredCheck(options);
|
|
116
|
+
if (result && typeof result.exitCode === 'number') {
|
|
117
|
+
process.exitCode = result.exitCode;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ── status — read-only cross-layer enforcement report (ID851) ──
|
|
122
|
+
// GET /api/v1/enforcement-status — no GitHub writes. Distinct from `coderifts hook status`.
|
|
123
|
+
program
|
|
124
|
+
.command('status [repo]')
|
|
125
|
+
.description('Show cross-layer enforcement status (Runtime / Merge / Deploy) for a repo — read-only')
|
|
126
|
+
.option('--repo <owner/repo>', 'Repository (owner/repo); also accepted as a positional argument')
|
|
127
|
+
.option('--json', 'Machine-readable JSON (raw API body)')
|
|
128
|
+
.action(async (repoPositional, options) => {
|
|
129
|
+
const { runStatus } = require('../src/commands/status');
|
|
130
|
+
const result = await runStatus({
|
|
131
|
+
...options,
|
|
132
|
+
repo: options.repo || repoPositional || null,
|
|
133
|
+
});
|
|
134
|
+
if (result && typeof result.exitCode === 'number') {
|
|
135
|
+
process.exitCode = result.exitCode;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ── enforce — orchestrate existing setup commands (ID851 step 2) ──
|
|
140
|
+
// Dry-run by default; --apply threads into setup-required-check / hook.install.
|
|
141
|
+
// No new GitHub writes with the CodeRifts API key (user's gh for merge; local fs for hook).
|
|
142
|
+
program
|
|
143
|
+
.command('enforce [repo]')
|
|
144
|
+
.description('Close enforcement gaps by chaining setup-required-check / hook / deploy guidance (dry-run default; use --apply to mutate)')
|
|
145
|
+
.option('--repo <owner/repo>', 'Repository (owner/repo); also accepted as a positional argument')
|
|
146
|
+
.option('--apply', 'Actually run underlying setup commands (default: dry-run only)')
|
|
147
|
+
.option('--json', 'Machine-readable JSON result')
|
|
148
|
+
.action(async (repoPositional, options) => {
|
|
149
|
+
const { runEnforce } = require('../src/commands/enforce');
|
|
150
|
+
const result = await runEnforce({
|
|
151
|
+
...options,
|
|
152
|
+
repo: options.repo || repoPositional || null,
|
|
153
|
+
apply: !!options.apply,
|
|
154
|
+
});
|
|
155
|
+
if (result && typeof result.exitCode === 'number') {
|
|
156
|
+
process.exitCode = result.exitCode;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ── agent-setup — emit six agent-host rule files (vendored from app generator) ──
|
|
161
|
+
program
|
|
162
|
+
.command('agent-setup')
|
|
163
|
+
.description('Write AGENTS.md / CLAUDE.md / Cursor / Copilot / LangGraph / OpenAI agent rule files')
|
|
164
|
+
.option('--out <dir>', 'Target directory (default: current working directory)')
|
|
165
|
+
.option('--check', 'Exit 0 if on-disk files match embedded content; exit 1 on drift')
|
|
166
|
+
.option('--force', 'Overwrite existing files (default: skip collisions)')
|
|
167
|
+
.action((options) => {
|
|
168
|
+
const { runAgentSetup } = require('../src/commands/agent-setup');
|
|
169
|
+
runAgentSetup(options, { exit: true });
|
|
170
|
+
});
|
|
171
|
+
|
|
63
172
|
// ── hook command group ──
|
|
64
173
|
const hookCmd = program
|
|
65
174
|
.command('hook')
|
|
@@ -107,4 +216,13 @@ corpusCmd
|
|
|
107
216
|
corpusVerify(options);
|
|
108
217
|
});
|
|
109
218
|
|
|
110
|
-
|
|
219
|
+
// parseAsync: required so async command actions (publish-gate, diff, deploy-gate, …)
|
|
220
|
+
// complete before the process ends. program.parse() discards the action promise and
|
|
221
|
+
// can leave process.exitCode at the default 0 even after a fail-closed gate printed errors.
|
|
222
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
223
|
+
console.error(err);
|
|
224
|
+
process.exitCode = typeof process.exitCode === 'number' && process.exitCode !== 0
|
|
225
|
+
? process.exitCode
|
|
226
|
+
: 1;
|
|
227
|
+
process.exit(process.exitCode);
|
|
228
|
+
});
|