opencode-codeops 2.4.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +49 -2
  3. package/_shared/discovery.md +2 -1
  4. package/_shared/spec-first-ordering.md +6 -2
  5. package/_shared/test-selection.md +130 -0
  6. package/bin/codeops-test.mjs +855 -0
  7. package/bin/lib/test-branch-policy.d.mts +51 -0
  8. package/bin/lib/test-branch-policy.mjs +185 -0
  9. package/bin/lib/test-map-validate.d.mts +42 -0
  10. package/bin/lib/test-map-validate.mjs +618 -0
  11. package/bin/lib/test-map.d.mts +114 -0
  12. package/bin/lib/test-map.mjs +384 -0
  13. package/bin/lib/test-provenance.d.mts +73 -0
  14. package/bin/lib/test-provenance.mjs +188 -0
  15. package/bin/lib/test-runner.d.mts +72 -0
  16. package/bin/lib/test-runner.mjs +350 -0
  17. package/bin/lib/test-selection-changes.d.mts +76 -0
  18. package/bin/lib/test-selection-changes.mjs +272 -0
  19. package/bin/lib/test-selection-explain.mjs +75 -0
  20. package/bin/lib/test-selection-glob.d.mts +32 -0
  21. package/bin/lib/test-selection-glob.mjs +206 -0
  22. package/bin/lib/test-selection.d.mts +80 -0
  23. package/bin/lib/test-selection.mjs +418 -0
  24. package/package.json +4 -2
  25. package/schemas/test-map.schema.json +164 -0
  26. package/scripts/__pycache__/codeops_plan.cpython-314.pyc +0 -0
  27. package/scripts/__pycache__/install_agents.cpython-314.pyc +0 -0
  28. package/scripts/discovery-toggle.mjs +236 -0
  29. package/skills/analyze-project/SKILL.md +9 -2
  30. package/skills/exec-plan/SKILL.md +16 -8
  31. package/skills/exec-plan/execution-protocol.md +15 -2
  32. package/skills/git-commit/SKILL.md +1 -1
  33. package/skills/make-plan/SKILL.md +10 -3
  34. package/skills/make-plan/quality-checklist.md +4 -0
  35. package/skills/make-plan/templates.md +19 -1
  36. package/skills/organize-tests/SKILL.md +169 -0
  37. package/skills/organize-tests/references/agents-testing-block.md +80 -0
  38. package/skills/organize-tests/references/analysis-protocol.md +94 -0
  39. package/skills/organize-tests/references/ci-github-actions.md +178 -0
  40. package/skills/organize-tests/references/language-adapters.md +54 -0
  41. package/skills/organize-tests/references/map-health.md +47 -0
  42. package/skills/organize-tests/references/project-wrapper.md +72 -0
  43. package/skills/organize-tests/references/shadow-mode.md +60 -0
  44. package/skills/setup-codeops/SKILL.md +30 -3
  45. package/standards/coding-standards-full.md +28 -0
  46. package/standards/coding-standards.md +29 -0
  47. package/scripts/__pycache__/codeops_plan.cpython-312.pyc +0 -0
  48. 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