opencode-codeops 2.5.0 → 2.6.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/CHANGELOG.md +13 -0
- package/README.md +39 -0
- package/_shared/spec-first-ordering.md +6 -2
- package/_shared/test-selection.md +130 -0
- package/bin/codeops-test.mjs +855 -0
- package/bin/lib/test-branch-policy.d.mts +51 -0
- package/bin/lib/test-branch-policy.mjs +185 -0
- package/bin/lib/test-map-validate.d.mts +42 -0
- package/bin/lib/test-map-validate.mjs +618 -0
- package/bin/lib/test-map.d.mts +114 -0
- package/bin/lib/test-map.mjs +384 -0
- package/bin/lib/test-provenance.d.mts +73 -0
- package/bin/lib/test-provenance.mjs +188 -0
- package/bin/lib/test-runner.d.mts +72 -0
- package/bin/lib/test-runner.mjs +350 -0
- package/bin/lib/test-selection-changes.d.mts +76 -0
- package/bin/lib/test-selection-changes.mjs +272 -0
- package/bin/lib/test-selection-explain.mjs +75 -0
- package/bin/lib/test-selection-glob.d.mts +32 -0
- package/bin/lib/test-selection-glob.mjs +206 -0
- package/bin/lib/test-selection.d.mts +80 -0
- package/bin/lib/test-selection.mjs +418 -0
- package/package.json +4 -2
- package/schemas/test-map.schema.json +164 -0
- package/scripts/__pycache__/codeops_plan.cpython-314.pyc +0 -0
- package/scripts/__pycache__/install_agents.cpython-314.pyc +0 -0
- package/skills/analyze-project/SKILL.md +8 -1
- package/skills/exec-plan/SKILL.md +16 -8
- package/skills/exec-plan/execution-protocol.md +15 -2
- package/skills/git-commit/SKILL.md +1 -1
- package/skills/make-plan/SKILL.md +10 -3
- package/skills/make-plan/quality-checklist.md +4 -0
- package/skills/make-plan/templates.md +19 -1
- package/skills/organize-tests/SKILL.md +169 -0
- package/skills/organize-tests/references/agents-testing-block.md +80 -0
- package/skills/organize-tests/references/analysis-protocol.md +94 -0
- package/skills/organize-tests/references/ci-github-actions.md +178 -0
- package/skills/organize-tests/references/language-adapters.md +54 -0
- package/skills/organize-tests/references/map-health.md +47 -0
- package/skills/organize-tests/references/project-wrapper.md +72 -0
- package/skills/organize-tests/references/shadow-mode.md +60 -0
- package/standards/coding-standards-full.md +28 -0
- package/standards/coding-standards.md +29 -0
- package/scripts/__pycache__/codeops_plan.cpython-312.pyc +0 -0
- package/scripts/__pycache__/install_agents.cpython-312.pyc +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for branch classification and plan generation.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Branch classes a ref can resolve to. */
|
|
6
|
+
export declare const BRANCH_CLASSES: readonly string[]
|
|
7
|
+
|
|
8
|
+
/** Classification result. */
|
|
9
|
+
export interface BranchClassification {
|
|
10
|
+
class: string
|
|
11
|
+
gate: string
|
|
12
|
+
full: boolean
|
|
13
|
+
warning: { code: string; message: string } | null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Choose a gate for a branch class. */
|
|
17
|
+
export declare function gateForClass(
|
|
18
|
+
branchClass: string,
|
|
19
|
+
options?: { ci?: boolean }
|
|
20
|
+
): string
|
|
21
|
+
|
|
22
|
+
/** Classify a branch or tag. */
|
|
23
|
+
export declare function classifyRef(
|
|
24
|
+
ref: string,
|
|
25
|
+
options?: { branches?: object; isTag?: boolean; ci?: boolean }
|
|
26
|
+
): BranchClassification
|
|
27
|
+
|
|
28
|
+
/** Decide whether a nightly run must execute. */
|
|
29
|
+
export declare function nightlyDecision(input?: {
|
|
30
|
+
changed?: string[]
|
|
31
|
+
map?: object | null
|
|
32
|
+
marker?: string | null
|
|
33
|
+
}): { mode: "full" | "skip" | "groups"; reason: string | null; relevant: string[] }
|
|
34
|
+
|
|
35
|
+
/** Build the CI plan JSON. */
|
|
36
|
+
export declare function buildPlan(input: {
|
|
37
|
+
selection: object
|
|
38
|
+
gate: string
|
|
39
|
+
total: number
|
|
40
|
+
}): {
|
|
41
|
+
schema: number
|
|
42
|
+
gate: string
|
|
43
|
+
mode: string
|
|
44
|
+
full: boolean
|
|
45
|
+
selected: number
|
|
46
|
+
total: number
|
|
47
|
+
mapHash: string | null
|
|
48
|
+
matrix: { include: Array<{ group: string; command: string[]; cwd: string }> }
|
|
49
|
+
fullCommand: string[] | string[][] | null
|
|
50
|
+
reason: string | null
|
|
51
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Branch classification and CI plan shaping.
|
|
4
|
+
*
|
|
5
|
+
* A branch name is not a security boundary, but it decides which gate runs.
|
|
6
|
+
* The policy therefore uses the same narrow glob matcher as selection, applies
|
|
7
|
+
* a fixed precedence, and fails toward the stricter gate: acceptance beats
|
|
8
|
+
* release, and an unknown branch uses the integration policy with a visible
|
|
9
|
+
* warning.
|
|
10
|
+
*
|
|
11
|
+
* @module lib/test-branch-policy
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { DEFAULT_BRANCHES } from "./test-map.mjs"
|
|
15
|
+
import { matchesAnyGlob } from "./test-selection-glob.mjs"
|
|
16
|
+
|
|
17
|
+
/** Branch classes a ref can resolve to. */
|
|
18
|
+
export const BRANCH_CLASSES = Object.freeze(["integration", "feature", "release", "acceptance", "unknown"])
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Choose the gate for a branch class.
|
|
22
|
+
*
|
|
23
|
+
* `ci` selects the CI context, where a feature branch runs the PR gate. Local
|
|
24
|
+
* feature work uses the smaller task gate.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} branchClass Branch class.
|
|
27
|
+
* @param {object} [options] Gate options.
|
|
28
|
+
* @param {boolean} [options.ci=false] Whether this is a CI run.
|
|
29
|
+
* @returns {string} Gate name accepted by the CLI.
|
|
30
|
+
* @example
|
|
31
|
+
* gateForClass("feature", { ci: true }) // "pr"
|
|
32
|
+
*/
|
|
33
|
+
export function gateForClass(branchClass, { ci = false } = {}) {
|
|
34
|
+
switch (branchClass) {
|
|
35
|
+
case "release":
|
|
36
|
+
return "release"
|
|
37
|
+
case "acceptance":
|
|
38
|
+
return "acceptance"
|
|
39
|
+
case "integration":
|
|
40
|
+
return "pr"
|
|
41
|
+
case "feature":
|
|
42
|
+
return ci ? "pr" : "task"
|
|
43
|
+
default:
|
|
44
|
+
return "pr"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check whether a ref is a release tag.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} ref Ref name.
|
|
52
|
+
* @param {object} [branches] Branch policy.
|
|
53
|
+
* @returns {boolean} `true` when the ref matches `releaseTags`.
|
|
54
|
+
*/
|
|
55
|
+
function isReleaseTag(ref, branches) {
|
|
56
|
+
return matchesAnyGlob(ref, branches.releaseTags ?? DEFAULT_BRANCHES.releaseTags)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Match a ref against one policy list.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} ref Ref name.
|
|
63
|
+
* @param {string[]} patterns Branch patterns.
|
|
64
|
+
* @returns {boolean} `true` when any pattern matches.
|
|
65
|
+
*/
|
|
66
|
+
function matchesBranch(ref, patterns) {
|
|
67
|
+
return matchesAnyGlob(ref, patterns ?? [])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Classify a branch or tag.
|
|
72
|
+
*
|
|
73
|
+
* Precedence is tag, acceptance, release, integration, feature. A ref that
|
|
74
|
+
* matches no list is `unknown` and uses the integration gate with a warning.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} ref Branch or tag name.
|
|
77
|
+
* @param {object} [options] Classification options.
|
|
78
|
+
* @param {object} [options.branches] Branch policy override.
|
|
79
|
+
* @param {boolean} [options.isTag=false] Whether the ref is a tag.
|
|
80
|
+
* @param {boolean} [options.ci=false] Whether this is a CI run.
|
|
81
|
+
* @returns {{class: string, gate: string, full: boolean, warning: {code: string, message: string}|null}}
|
|
82
|
+
* @example
|
|
83
|
+
* classifyRef("release/2026.11") // { class: "release", gate: "release", full: true, ... }
|
|
84
|
+
*/
|
|
85
|
+
export function classifyRef(ref, { branches = DEFAULT_BRANCHES, isTag = false, ci = false } = {}) {
|
|
86
|
+
const name = String(ref ?? "")
|
|
87
|
+
|
|
88
|
+
if ((isTag || isReleaseTag(name, branches)) && matchesBranch(name, branches.releaseTags ?? DEFAULT_BRANCHES.releaseTags)) {
|
|
89
|
+
return { class: "release", gate: "release", full: true, warning: null }
|
|
90
|
+
}
|
|
91
|
+
if (matchesBranch(name, branches.acceptance)) {
|
|
92
|
+
return { class: "acceptance", gate: "acceptance", full: true, warning: null }
|
|
93
|
+
}
|
|
94
|
+
if (matchesBranch(name, branches.release)) {
|
|
95
|
+
return { class: "release", gate: "release", full: true, warning: null }
|
|
96
|
+
}
|
|
97
|
+
if (matchesBranch(name, branches.integration)) {
|
|
98
|
+
return { class: "integration", gate: gateForClass("integration", { ci }), full: false, warning: null }
|
|
99
|
+
}
|
|
100
|
+
if (matchesBranch(name, branches.feature)) {
|
|
101
|
+
return { class: "feature", gate: gateForClass("feature", { ci }), full: false, warning: null }
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
class: "unknown",
|
|
105
|
+
gate: gateForClass("integration", { ci }),
|
|
106
|
+
full: false,
|
|
107
|
+
warning: { code: "branch.unknown", message: `branch ${name} matches no policy class; using integration` },
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Decide whether a nightly run must execute or can skip.
|
|
113
|
+
*
|
|
114
|
+
* A missing marker always runs full. A path counts as relevant when it is owned
|
|
115
|
+
* by the map, matches `fullOnPaths`, or is a toolchain/config path. A path that
|
|
116
|
+
* is both ignored and full-on remains relevant: safety wins.
|
|
117
|
+
*
|
|
118
|
+
* @param {object} input Decision input.
|
|
119
|
+
* @param {string[]} [input.changed=[]] Changed paths.
|
|
120
|
+
* @param {object|null} [input.map=null] Loaded map.
|
|
121
|
+
* @param {string|null} [input.marker=null] Last successful full-run SHA.
|
|
122
|
+
* @returns {{mode: "full"|"skip"|"groups", reason: string|null, relevant: string[]}}
|
|
123
|
+
* @example
|
|
124
|
+
* nightlyDecision({ changed: ["docs/a.md"], map, marker: "abc" }).mode // "skip"
|
|
125
|
+
*/
|
|
126
|
+
export function nightlyDecision({ changed = [], map = null, marker = null } = {}) {
|
|
127
|
+
if (!marker) return { mode: "full", reason: "nightly.no-marker", relevant: changed }
|
|
128
|
+
|
|
129
|
+
const policy = map?.policy ?? {}
|
|
130
|
+
const ignore = (policy.ignorePaths ?? []).map((entry) => entry.glob)
|
|
131
|
+
const fullOn = policy.fullOnPaths ?? []
|
|
132
|
+
const owned = (map?.groups ?? []).flatMap((group) => [...(group.sources ?? []), ...(group.tests ?? [])])
|
|
133
|
+
|
|
134
|
+
const relevant = changed.filter((path) => {
|
|
135
|
+
if (matchesAnyGlob(path, fullOn)) return true
|
|
136
|
+
if (matchesAnyGlob(path, owned)) return true
|
|
137
|
+
// An unowned path is still a real change. Running the full suite is the
|
|
138
|
+
// safe response; skipping it would hide a new source file from every group.
|
|
139
|
+
return !matchesAnyGlob(path, ignore)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
if (relevant.length === 0) return { mode: "skip", reason: "no-relevant-change", relevant }
|
|
143
|
+
return { mode: "groups", reason: null, relevant }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Build the CI plan JSON from a selection.
|
|
148
|
+
*
|
|
149
|
+
* The matrix key is always present, even for a full or docs-only result, so the
|
|
150
|
+
* workflow never has to special-case a missing value. Full is a mode, never a
|
|
151
|
+
* fake group ID.
|
|
152
|
+
*
|
|
153
|
+
* @param {object} input Plan input.
|
|
154
|
+
* @param {object} input.selection Selection result.
|
|
155
|
+
* @param {string} input.gate Gate name.
|
|
156
|
+
* @param {number} input.total Total group count.
|
|
157
|
+
* @returns {object} Plan JSON.
|
|
158
|
+
* @example
|
|
159
|
+
* buildPlan({ selection, gate: "pr", total: 8 })
|
|
160
|
+
*/
|
|
161
|
+
export function buildPlan({ selection, gate, total }) {
|
|
162
|
+
const matrix =
|
|
163
|
+
selection.mode === "groups"
|
|
164
|
+
? {
|
|
165
|
+
include: (selection.commands ?? []).map((command) => ({
|
|
166
|
+
group: command.id,
|
|
167
|
+
command: command.argv,
|
|
168
|
+
cwd: command.cwd,
|
|
169
|
+
})),
|
|
170
|
+
}
|
|
171
|
+
: { include: [] }
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
schema: 1,
|
|
175
|
+
gate,
|
|
176
|
+
mode: selection.mode,
|
|
177
|
+
full: selection.mode === "full" || gate === "release" || gate === "acceptance",
|
|
178
|
+
selected: (selection.selected ?? []).length,
|
|
179
|
+
total,
|
|
180
|
+
mapHash: selection.mapHash ?? null,
|
|
181
|
+
matrix,
|
|
182
|
+
fullCommand: selection.fullCommand ?? null,
|
|
183
|
+
reason: selection.reason ?? null,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for test-map validation and health checks.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { TestMap, TestMapGroup } from "./test-map.mjs"
|
|
6
|
+
|
|
7
|
+
/** Supported group tiers. */
|
|
8
|
+
export declare const GROUP_TIERS: readonly TestMapGroup["tier"][]
|
|
9
|
+
|
|
10
|
+
/** Supported branch classes. */
|
|
11
|
+
export declare const BRANCH_CLASSES: readonly string[]
|
|
12
|
+
|
|
13
|
+
/** Supported acceptance modes. */
|
|
14
|
+
export declare const ACCEPTANCE_MODES: readonly string[]
|
|
15
|
+
|
|
16
|
+
/** Supported per-slice acceptance exception modes. */
|
|
17
|
+
export declare const SLICE_ACCEPTANCE_MODES: readonly string[]
|
|
18
|
+
|
|
19
|
+
/** Rules that warn in normal mode and fail under strict mode. */
|
|
20
|
+
export declare const WARNING_CODES: readonly string[]
|
|
21
|
+
|
|
22
|
+
/** One validation diagnostic. */
|
|
23
|
+
export interface ValidationDiagnostic {
|
|
24
|
+
code: string
|
|
25
|
+
message: string
|
|
26
|
+
path?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Validation result. */
|
|
30
|
+
export interface ValidationReport {
|
|
31
|
+
errors: ValidationDiagnostic[]
|
|
32
|
+
warnings: ValidationDiagnostic[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Validate a map, optionally with a tracked-file inventory for health checks. */
|
|
36
|
+
export declare function validateMap(
|
|
37
|
+
map: TestMap,
|
|
38
|
+
options?: { files?: string[] | null }
|
|
39
|
+
): ValidationReport
|
|
40
|
+
|
|
41
|
+
/** Map a report to the documented exit code. */
|
|
42
|
+
export declare function exitCodeFor(report: ValidationReport, options?: { strict?: boolean }): number
|