@neurcode/action 0.2.2 → 0.3.0-rc.6

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.
@@ -1,201 +0,0 @@
1
- import {
2
- CLI_JSON_CONTRACT_VERSION,
3
- getMinimumCompatiblePeerVersion,
4
- isSemverAtLeast,
5
- parseCliCompatJsonPayload,
6
- RUNTIME_COMPATIBILITY_CONTRACT_ID,
7
- RUNTIME_COMPATIBILITY_CONTRACT_VERSION,
8
- RuntimeComponent,
9
- RuntimeMinimumPeerVersions,
10
- } from '@neurcode-ai/contracts';
11
-
12
- export interface ParsedComponentCompatibility {
13
- source: 'cli' | 'api';
14
- contractId: string;
15
- runtimeContractVersion: string;
16
- cliJsonContractVersion: string;
17
- component: RuntimeComponent;
18
- componentVersion: string;
19
- minimumPeerVersions: RuntimeMinimumPeerVersions;
20
- }
21
-
22
- function asRecord(value: unknown, label: string): Record<string, unknown> {
23
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
24
- throw new Error(`${label}: expected object`);
25
- }
26
- return value as Record<string, unknown>;
27
- }
28
-
29
- function asString(record: Record<string, unknown>, key: string, label: string): string {
30
- const value = record[key];
31
- if (typeof value !== 'string' || !value.trim()) {
32
- throw new Error(`${label}: expected ${key}:string`);
33
- }
34
- return value.trim();
35
- }
36
-
37
- function asRuntimeComponent(value: string, label: string): RuntimeComponent {
38
- if (value === 'cli' || value === 'action' || value === 'api') {
39
- return value;
40
- }
41
- throw new Error(`${label}: expected component "cli" | "action" | "api"`);
42
- }
43
-
44
- function parseMinimumPeerVersions(value: unknown, label: string): RuntimeMinimumPeerVersions {
45
- if (value === undefined || value === null) return {};
46
- const record = asRecord(value, `${label}.minimumPeerVersions`);
47
- const next: RuntimeMinimumPeerVersions = {};
48
- for (const component of ['cli', 'action', 'api'] as const) {
49
- const item = record[component];
50
- if (item === undefined) continue;
51
- if (typeof item !== 'string' || !item.trim()) {
52
- throw new Error(`${label}.minimumPeerVersions: expected ${component}:string`);
53
- }
54
- next[component] = item.trim();
55
- }
56
- return next;
57
- }
58
-
59
- function parseDescriptor(value: unknown, label: string): Omit<ParsedComponentCompatibility, 'source'> {
60
- const record = asRecord(value, label);
61
- return {
62
- contractId: asString(record, 'contractId', label),
63
- runtimeContractVersion: asString(record, 'runtimeContractVersion', label),
64
- cliJsonContractVersion: asString(record, 'cliJsonContractVersion', label),
65
- component: asRuntimeComponent(asString(record, 'component', label), label),
66
- componentVersion: asString(record, 'componentVersion', label),
67
- minimumPeerVersions: parseMinimumPeerVersions(record.minimumPeerVersions, label),
68
- };
69
- }
70
-
71
- export function parseCliCompatibilityPayload(value: unknown): ParsedComponentCompatibility {
72
- const parsed = parseCliCompatJsonPayload(value, 'action-cli-compat');
73
- if (parsed.success !== true) {
74
- throw new Error('action-cli-compat: success=false');
75
- }
76
- const descriptor = parseDescriptor(parsed.compatibility, 'action-cli-compat.compatibility');
77
- return {
78
- source: 'cli',
79
- ...descriptor,
80
- };
81
- }
82
-
83
- export function parseApiHealthCompatibilityPayload(value: unknown): ParsedComponentCompatibility | null {
84
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
85
- return null;
86
- }
87
- const record = value as Record<string, unknown>;
88
- if (!record.compatibility) {
89
- return null;
90
- }
91
- const descriptor = parseDescriptor(record.compatibility, 'action-api-compat.compatibility');
92
- return {
93
- source: 'api',
94
- ...descriptor,
95
- };
96
- }
97
-
98
- function validateVersionMinimum(
99
- label: string,
100
- actual: string,
101
- required: string | undefined,
102
- errors: string[]
103
- ): void {
104
- if (!required) return;
105
- const isCompatible = isSemverAtLeast(actual, required);
106
- if (isCompatible === null) {
107
- errors.push(`${label}: unable to compare versions (actual=${actual}, required=${required}).`);
108
- return;
109
- }
110
- if (!isCompatible) {
111
- errors.push(`${label}: actual=${actual} is below required=${required}.`);
112
- }
113
- }
114
-
115
- function validateContractEnvelope(
116
- descriptor: ParsedComponentCompatibility,
117
- expectedComponent: RuntimeComponent,
118
- errors: string[]
119
- ): void {
120
- if (descriptor.component !== expectedComponent) {
121
- errors.push(
122
- `${descriptor.source} compatibility payload expected component=${expectedComponent} but received ${descriptor.component}.`
123
- );
124
- }
125
- if (descriptor.contractId !== RUNTIME_COMPATIBILITY_CONTRACT_ID) {
126
- errors.push(
127
- `${descriptor.source} compatibility payload has unexpected contractId=${descriptor.contractId} (expected ${RUNTIME_COMPATIBILITY_CONTRACT_ID}).`
128
- );
129
- }
130
- if (descriptor.runtimeContractVersion !== RUNTIME_COMPATIBILITY_CONTRACT_VERSION) {
131
- errors.push(
132
- `${descriptor.source} compatibility payload has runtimeContractVersion=${descriptor.runtimeContractVersion} (expected ${RUNTIME_COMPATIBILITY_CONTRACT_VERSION}).`
133
- );
134
- }
135
- if (descriptor.cliJsonContractVersion !== CLI_JSON_CONTRACT_VERSION) {
136
- errors.push(
137
- `${descriptor.source} compatibility payload has cliJsonContractVersion=${descriptor.cliJsonContractVersion} (expected ${CLI_JSON_CONTRACT_VERSION}).`
138
- );
139
- }
140
- }
141
-
142
- export function validateActionHandshake(input: {
143
- actionVersion: string;
144
- cliCompatibility: ParsedComponentCompatibility;
145
- apiCompatibility?: ParsedComponentCompatibility | null;
146
- requireApiCompatibility?: boolean;
147
- }): string[] {
148
- const errors: string[] = [];
149
- const { actionVersion, cliCompatibility } = input;
150
- const apiCompatibility = input.apiCompatibility || null;
151
- const requireApiCompatibility = input.requireApiCompatibility === true;
152
-
153
- validateContractEnvelope(cliCompatibility, 'cli', errors);
154
- validateVersionMinimum(
155
- 'CLI version required by action',
156
- cliCompatibility.componentVersion,
157
- getMinimumCompatiblePeerVersion('action', 'cli'),
158
- errors
159
- );
160
- validateVersionMinimum(
161
- 'Action version required by CLI payload',
162
- actionVersion,
163
- cliCompatibility.minimumPeerVersions.action,
164
- errors
165
- );
166
-
167
- if (!apiCompatibility) {
168
- if (requireApiCompatibility) {
169
- errors.push('API compatibility payload missing while API compatibility handshake is required.');
170
- }
171
- return errors;
172
- }
173
-
174
- validateContractEnvelope(apiCompatibility, 'api', errors);
175
- validateVersionMinimum(
176
- 'API version required by action',
177
- apiCompatibility.componentVersion,
178
- getMinimumCompatiblePeerVersion('action', 'api'),
179
- errors
180
- );
181
- validateVersionMinimum(
182
- 'Action version required by API payload',
183
- actionVersion,
184
- apiCompatibility.minimumPeerVersions.action,
185
- errors
186
- );
187
- validateVersionMinimum(
188
- 'CLI version required by API payload',
189
- cliCompatibility.componentVersion,
190
- apiCompatibility.minimumPeerVersions.cli,
191
- errors
192
- );
193
- validateVersionMinimum(
194
- 'API version required by CLI payload',
195
- apiCompatibility.componentVersion,
196
- cliCompatibility.minimumPeerVersions.api,
197
- errors
198
- );
199
-
200
- return errors;
201
- }
@@ -1,116 +0,0 @@
1
- export type VerifyFallbackReason =
2
- | 'missing_plan_context'
3
- | 'verify_succeeded'
4
- | 'already_policy_only'
5
- | 'explicit_plan_id'
6
- | 'not_missing_plan_failure';
7
-
8
- export interface VerifyFallbackDecision {
9
- shouldRetryPolicyOnly: boolean;
10
- reason: VerifyFallbackReason;
11
- }
12
-
13
- export interface VerifyFallbackDecisionInput {
14
- verifyExitCode: number;
15
- policyOnlyRequested: boolean;
16
- hasExplicitPlanId: boolean;
17
- verifyOutput: string;
18
- }
19
-
20
- export interface VerifyArgsInput {
21
- baseRef: string;
22
- planId?: string;
23
- projectId?: string;
24
- policyOnly: boolean;
25
- record: boolean;
26
- compiledPolicyPath?: string;
27
- changeContractPath?: string;
28
- enforceChangeContract: boolean;
29
- strictArtifacts?: boolean;
30
- }
31
-
32
- export interface EnterpriseEnforcementInput {
33
- enterpriseMode: boolean;
34
- verifyPolicyOnly: boolean;
35
- enforceChangeContract?: boolean;
36
- enforceStrictVerification?: boolean;
37
- }
38
-
39
- export interface EnterpriseEnforcementDecision {
40
- enforceChangeContract: boolean;
41
- enforceStrictVerification: boolean;
42
- }
43
-
44
- const ANSI_PATTERN = /\u001b\[[0-9;]*m/g;
45
-
46
- const MISSING_PLAN_VERIFY_PATTERNS = [
47
- 'plan id is required in strict mode',
48
- 'run "neurcode plan" first',
49
- 'pass --plan-id',
50
- '"mode": "plan_required"',
51
- '"mode":"plan_required"',
52
- 'missing plan context',
53
- 'plan context missing',
54
- 'no approved plan context',
55
- ];
56
-
57
- function stripAnsi(value: string): string {
58
- return value.replace(ANSI_PATTERN, '');
59
- }
60
-
61
- export function isMissingPlanVerificationFailure(output: string): boolean {
62
- const normalized = stripAnsi(output).toLowerCase();
63
- return MISSING_PLAN_VERIFY_PATTERNS.some((pattern) => normalized.includes(pattern));
64
- }
65
-
66
- export function buildVerifyArgs(input: VerifyArgsInput): string[] {
67
- const args = ['verify', '--json', '--base', input.baseRef];
68
- if (input.policyOnly) {
69
- args.push('--policy-only');
70
- } else if (input.planId) {
71
- args.push('--plan-id', input.planId);
72
- }
73
- if (input.projectId) args.push('--project-id', input.projectId);
74
- if (input.compiledPolicyPath) args.push('--compiled-policy', input.compiledPolicyPath);
75
- if (input.changeContractPath) args.push('--change-contract', input.changeContractPath);
76
- if (input.enforceChangeContract) args.push('--enforce-change-contract');
77
- if (input.strictArtifacts) args.push('--strict-artifacts');
78
- if (input.record) args.push('--record');
79
- return args;
80
- }
81
-
82
- export function resolveEnterpriseEnforcement(
83
- input: EnterpriseEnforcementInput
84
- ): EnterpriseEnforcementDecision {
85
- const enforceChangeContract =
86
- typeof input.enforceChangeContract === 'boolean'
87
- ? input.enforceChangeContract
88
- : (input.enterpriseMode && !input.verifyPolicyOnly);
89
- const enforceStrictVerification =
90
- typeof input.enforceStrictVerification === 'boolean'
91
- ? input.enforceStrictVerification
92
- : input.enterpriseMode;
93
-
94
- return {
95
- enforceChangeContract,
96
- enforceStrictVerification,
97
- };
98
- }
99
-
100
- export function getVerifyFallbackDecision(
101
- input: VerifyFallbackDecisionInput
102
- ): VerifyFallbackDecision {
103
- if (input.verifyExitCode === 0) {
104
- return { shouldRetryPolicyOnly: false, reason: 'verify_succeeded' };
105
- }
106
- if (input.policyOnlyRequested) {
107
- return { shouldRetryPolicyOnly: false, reason: 'already_policy_only' };
108
- }
109
- if (input.hasExplicitPlanId) {
110
- return { shouldRetryPolicyOnly: false, reason: 'explicit_plan_id' };
111
- }
112
- if (isMissingPlanVerificationFailure(input.verifyOutput)) {
113
- return { shouldRetryPolicyOnly: true, reason: 'missing_plan_context' };
114
- }
115
- return { shouldRetryPolicyOnly: false, reason: 'not_missing_plan_failure' };
116
- }
@@ -1,214 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
-
4
- import {
5
- buildVerifyArgs,
6
- getVerifyFallbackDecision,
7
- isMissingPlanVerificationFailure,
8
- resolveEnterpriseEnforcement,
9
- } from '../src/verify-mode';
10
- import {
11
- parseApiHealthCompatibilityPayload,
12
- parseCliCompatibilityPayload,
13
- validateActionHandshake,
14
- } from '../src/runtime-compat';
15
-
16
- test('verify args remain plan-aware when no plan_id is provided', () => {
17
- const args = buildVerifyArgs({
18
- baseRef: 'origin/main',
19
- policyOnly: false,
20
- record: true,
21
- enforceChangeContract: false,
22
- });
23
-
24
- assert.deepEqual(args, ['verify', '--json', '--base', 'origin/main', '--record']);
25
- assert.equal(args.includes('--policy-only'), false);
26
- assert.equal(args.includes('--plan-id'), false);
27
- });
28
-
29
- test('verify args enforce explicit plan_id when provided', () => {
30
- const args = buildVerifyArgs({
31
- baseRef: 'origin/main',
32
- policyOnly: false,
33
- record: true,
34
- planId: 'plan_123',
35
- enforceChangeContract: false,
36
- });
37
-
38
- assert.deepEqual(args, [
39
- 'verify',
40
- '--json',
41
- '--base',
42
- 'origin/main',
43
- '--plan-id',
44
- 'plan_123',
45
- '--record',
46
- ]);
47
- });
48
-
49
- test('verify args include strict artifact enforcement when requested', () => {
50
- const args = buildVerifyArgs({
51
- baseRef: 'origin/main',
52
- policyOnly: false,
53
- record: false,
54
- planId: 'plan_123',
55
- enforceChangeContract: true,
56
- strictArtifacts: true,
57
- });
58
-
59
- assert.equal(args.includes('--strict-artifacts'), true);
60
- assert.equal(args.includes('--enforce-change-contract'), true);
61
- });
62
-
63
- test('fallback decision retries policy-only when plan context is missing and no explicit plan_id exists', () => {
64
- const decision = getVerifyFallbackDecision({
65
- verifyExitCode: 2,
66
- policyOnlyRequested: false,
67
- hasExplicitPlanId: false,
68
- verifyOutput:
69
- 'verification failed: plan id is required in strict mode. run "neurcode plan" first.',
70
- });
71
-
72
- assert.equal(decision.shouldRetryPolicyOnly, true);
73
- assert.equal(decision.reason, 'missing_plan_context');
74
- });
75
-
76
- test('fallback decision does not retry policy-only when explicit plan_id was supplied', () => {
77
- const decision = getVerifyFallbackDecision({
78
- verifyExitCode: 2,
79
- policyOnlyRequested: false,
80
- hasExplicitPlanId: true,
81
- verifyOutput:
82
- 'verification failed: plan id is required in strict mode. run "neurcode plan" first.',
83
- });
84
-
85
- assert.equal(decision.shouldRetryPolicyOnly, false);
86
- assert.equal(decision.reason, 'explicit_plan_id');
87
- });
88
-
89
- test('missing-plan matcher recognizes canonical strict-mode errors', () => {
90
- assert.equal(
91
- isMissingPlanVerificationFailure('Error: plan context missing for current diff'),
92
- true
93
- );
94
- assert.equal(
95
- isMissingPlanVerificationFailure('policy violations found in changed files'),
96
- false
97
- );
98
- });
99
-
100
- test('enterprise mode auto-enables strict and change-contract enforcement for plan-aware runs', () => {
101
- const decision = resolveEnterpriseEnforcement({
102
- enterpriseMode: true,
103
- verifyPolicyOnly: false,
104
- });
105
-
106
- assert.equal(decision.enforceChangeContract, true);
107
- assert.equal(decision.enforceStrictVerification, true);
108
- });
109
-
110
- test('enterprise auto mode relaxes change-contract hard-fail for policy-only runs', () => {
111
- const decision = resolveEnterpriseEnforcement({
112
- enterpriseMode: true,
113
- verifyPolicyOnly: true,
114
- });
115
-
116
- assert.equal(decision.enforceChangeContract, false);
117
- assert.equal(decision.enforceStrictVerification, true);
118
- });
119
-
120
- test('explicit enforcement inputs override enterprise auto behavior', () => {
121
- const decision = resolveEnterpriseEnforcement({
122
- enterpriseMode: true,
123
- verifyPolicyOnly: true,
124
- enforceChangeContract: false,
125
- enforceStrictVerification: true,
126
- });
127
-
128
- assert.equal(decision.enforceChangeContract, false);
129
- assert.equal(decision.enforceStrictVerification, true);
130
- });
131
-
132
- test('compatibility parser accepts valid CLI compat payload', () => {
133
- const parsed = parseCliCompatibilityPayload({
134
- success: true,
135
- component: 'cli',
136
- componentVersion: '0.9.35',
137
- timestamp: new Date().toISOString(),
138
- compatibility: {
139
- contractId: 'neurcode-runtime-compatibility',
140
- runtimeContractVersion: '2026-04-04',
141
- cliJsonContractVersion: '2026-04-04',
142
- component: 'cli',
143
- componentVersion: '0.9.35',
144
- minimumPeerVersions: {
145
- action: '0.2.1',
146
- api: '0.2.0',
147
- },
148
- },
149
- });
150
-
151
- assert.equal(parsed.component, 'cli');
152
- assert.equal(parsed.componentVersion, '0.9.35');
153
- });
154
-
155
- test('compatibility handshake reports violation when CLI is below required minimum', () => {
156
- const errors = validateActionHandshake({
157
- actionVersion: '0.2.1',
158
- cliCompatibility: {
159
- source: 'cli',
160
- contractId: 'neurcode-runtime-compatibility',
161
- runtimeContractVersion: '2026-04-04',
162
- cliJsonContractVersion: '2026-04-04',
163
- component: 'cli',
164
- componentVersion: '0.9.10',
165
- minimumPeerVersions: {
166
- action: '0.2.1',
167
- api: '0.2.0',
168
- },
169
- },
170
- });
171
-
172
- assert.equal(errors.length > 0, true);
173
- assert.equal(errors.some((item) => item.includes('CLI version required by action')), true);
174
- });
175
-
176
- test('compatibility handshake validates API payload when present', () => {
177
- const apiCompatibility = parseApiHealthCompatibilityPayload({
178
- status: 'ok',
179
- version: '0.2.0',
180
- compatibility: {
181
- contractId: 'neurcode-runtime-compatibility',
182
- runtimeContractVersion: '2026-04-04',
183
- cliJsonContractVersion: '2026-04-04',
184
- component: 'api',
185
- componentVersion: '0.2.0',
186
- minimumPeerVersions: {
187
- cli: '0.9.35',
188
- action: '0.2.1',
189
- },
190
- },
191
- });
192
-
193
- assert.ok(apiCompatibility);
194
-
195
- const errors = validateActionHandshake({
196
- actionVersion: '0.2.1',
197
- cliCompatibility: {
198
- source: 'cli',
199
- contractId: 'neurcode-runtime-compatibility',
200
- runtimeContractVersion: '2026-04-04',
201
- cliJsonContractVersion: '2026-04-04',
202
- component: 'cli',
203
- componentVersion: '0.9.35',
204
- minimumPeerVersions: {
205
- action: '0.2.1',
206
- api: '0.2.0',
207
- },
208
- },
209
- apiCompatibility,
210
- requireApiCompatibility: true,
211
- });
212
-
213
- assert.deepEqual(errors, []);
214
- });
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "commonjs",
5
- "lib": ["ES2022"],
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "declaration": false,
13
- "sourceMap": true,
14
- "resolveJsonModule": true
15
- },
16
- "include": ["src/**/*"],
17
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
18
- }
19
-