@brainervirus/workit-core 2.5.0 → 2.7.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/package.json +1 -1
- package/scripts/analyze-release-scope.ts +99 -8
- package/scripts/bundled-deps.json +133 -0
- package/scripts/publish-changed-packages.ts +28 -16
- package/scripts/rewrite-workspace-deps.ts +11 -5
- package/scripts/sync-release-manifests.ts +5 -1
- package/skills/workit-behavioral-tdd/SKILL.md +15 -9
- package/skills/workit-debug/SKILL.md +2 -1
- package/src/check-config.ts +294 -0
- package/src/checks.ts +480 -0
- package/src/core/detect-hosts.ts +9 -3
- package/src/core/doctor.ts +61 -0
- package/src/core/host-install.ts +144 -1
- package/src/core/methods.ts +5 -2
- package/src/core/setup.ts +27 -15
- package/src/core/support-matrix.ts +4 -0
- package/src/core/task-context.ts +6 -2
- package/src/core/task-contract.ts +70 -12
- package/src/core/task-engine.ts +120 -10
- package/src/core/task-evaluation.ts +176 -1
- package/src/core/uninstall.ts +107 -4
- package/src/git/rev.ts +50 -0
- package/src/hooks/context.ts +44 -5
- package/src/hooks/handle.ts +15 -4
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// them; merge-backs and the release's own manifest sync never do.
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
|
+
import BUNDLED_DEPS_JSON from "./bundled-deps.json" with { type: "json" };
|
|
9
10
|
|
|
10
11
|
export const RELEASE_PACKAGES = [
|
|
11
12
|
"workit-core",
|
|
@@ -15,8 +16,90 @@ export const RELEASE_PACKAGES = [
|
|
|
15
16
|
"workit-cursor",
|
|
16
17
|
"workit-codex",
|
|
17
18
|
"workit-pi",
|
|
19
|
+
"workit-claude-code",
|
|
18
20
|
] as const;
|
|
19
21
|
|
|
22
|
+
type ReleasePackage = (typeof RELEASE_PACKAGES)[number];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Sources a package's published dist/ inlines from OUTSIDE its own directory.
|
|
26
|
+
* Every adapter build is a no-external `bun build`, so whatever it imports
|
|
27
|
+
* from another workspace package is copied into its bundle: a runtime
|
|
28
|
+
* dependency on that package does not reach the shipped code. A change in
|
|
29
|
+
* these sources must therefore republish the bundling package too. Verified
|
|
30
|
+
* against the build entries' metafiles by
|
|
31
|
+
* test/workit-core/bundled-sources.test.ts.
|
|
32
|
+
*/
|
|
33
|
+
const CORE = "packages/workit-core/"; // sources, skills, templates and package.json
|
|
34
|
+
// Cursor and Codex inline the MCP transport and its declared dependencies.
|
|
35
|
+
const MCP = ["packages/workit-mcp/src/", "packages/workit-mcp/package.json"];
|
|
36
|
+
// The bundled CLI inlines its sources, package.json (`workit --version`) and
|
|
37
|
+
// its third-party dependencies.
|
|
38
|
+
const CLI = ["packages/workit-cli/src/", "packages/workit-cli/package.json"];
|
|
39
|
+
export const BUNDLED_SOURCES: Partial<Record<ReleasePackage, readonly string[]>> = {
|
|
40
|
+
"workit-mcp": [CORE],
|
|
41
|
+
"workit-cli": [CORE],
|
|
42
|
+
"workit-opencode": [CORE],
|
|
43
|
+
"workit-cursor": [CORE, ...MCP],
|
|
44
|
+
"workit-codex": [CORE, ...MCP],
|
|
45
|
+
"workit-pi": [CORE],
|
|
46
|
+
"workit-claude-code": [CORE, ...CLI],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Third-party packages each dist/ inlines, as bun.lock `packages` keys
|
|
51
|
+
* (`zod`, or `@opencode-ai/plugin/zod` for a nested copy). A lockfile change
|
|
52
|
+
* republishes a package only when one of these resolves to a different
|
|
53
|
+
* version, so dev-tooling bumps never release. Kept in sync with the bundle
|
|
54
|
+
* metafiles by test/workit-core/bundled-sources.test.ts.
|
|
55
|
+
*/
|
|
56
|
+
export const BUNDLED_DEPS = BUNDLED_DEPS_JSON as Partial<Record<ReleasePackage, string[]>>;
|
|
57
|
+
|
|
58
|
+
const LOCKFILE = "bun.lock";
|
|
59
|
+
|
|
60
|
+
/** `packages` key → resolved `name@version` from a bun.lock text (JSONC). */
|
|
61
|
+
export const lockResolutions = (text: string): Map<string, string> => {
|
|
62
|
+
// bun.lock is JSON with trailing commas.
|
|
63
|
+
const parsed = JSON.parse(text.replace(/,(\s*[}\]])/g, "$1")) as {
|
|
64
|
+
packages?: Record<string, unknown>;
|
|
65
|
+
};
|
|
66
|
+
const resolved = new Map<string, string>();
|
|
67
|
+
for (const [key, entry] of Object.entries(parsed.packages ?? {}))
|
|
68
|
+
if (Array.isArray(entry) && typeof entry[0] === "string") resolved.set(key, entry[0]);
|
|
69
|
+
return resolved;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const lockAt = (root: string, rev: string): Map<string, string> | null => {
|
|
73
|
+
try {
|
|
74
|
+
return lockResolutions(
|
|
75
|
+
execFileSync("git", ["show", `${rev}:${LOCKFILE}`], {
|
|
76
|
+
cwd: root,
|
|
77
|
+
encoding: "utf8",
|
|
78
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
79
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Packages whose inlined third-party resolutions differ between two revisions. */
|
|
88
|
+
export const lockChangedPackages = (root: string, from: string, to: string): ReleasePackage[] => {
|
|
89
|
+
const before = lockAt(root, from);
|
|
90
|
+
const after = lockAt(root, to);
|
|
91
|
+
if (before === null && after === null) return [];
|
|
92
|
+
return RELEASE_PACKAGES.filter((pkg) =>
|
|
93
|
+
(BUNDLED_DEPS[pkg] ?? []).some((key) => before?.get(key) !== after?.get(key)),
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** Every repository path whose change alters `pkg`'s published payload. */
|
|
98
|
+
export const payloadPaths = (pkg: ReleasePackage): string[] => [
|
|
99
|
+
`packages/${pkg}/`,
|
|
100
|
+
...(BUNDLED_SOURCES[pkg] ?? []),
|
|
101
|
+
];
|
|
102
|
+
|
|
20
103
|
/** The release pipeline's own version-sync commit: never a release trigger. */
|
|
21
104
|
const RELEASE_SYNC = /^chore\(release\): sync manifests\b/;
|
|
22
105
|
|
|
@@ -56,11 +139,15 @@ const subjectLevel = (commit: string): Level | null => {
|
|
|
56
139
|
// combined diff drops files identical to either parent — e.g. hotfix-branch
|
|
57
140
|
// back-merges), and -z returns raw NUL-delimited paths so spaces/non-ASCII
|
|
58
141
|
// are never C-quoted. NUL is fine in captured output, never in argv.
|
|
59
|
-
const commitsSince = (
|
|
142
|
+
const commitsSince = (
|
|
143
|
+
root: string,
|
|
144
|
+
from: string,
|
|
145
|
+
): { hash: string; message: string; files: string[] }[] => {
|
|
60
146
|
const hashes = g(root, ["log", "--reverse", "--format=%H", `${from}..HEAD`])
|
|
61
147
|
.split("\n")
|
|
62
148
|
.filter(Boolean);
|
|
63
149
|
return hashes.map((h) => ({
|
|
150
|
+
hash: h,
|
|
64
151
|
message: g(root, ["show", "-s", "--format=%B", h]),
|
|
65
152
|
files: g(root, ["diff-tree", "--no-commit-id", "--name-only", "-r", "-m", "--root", "-z", h])
|
|
66
153
|
.split("\0")
|
|
@@ -80,20 +167,24 @@ export function analyzeReleaseScope(root = process.cwd()): {
|
|
|
80
167
|
const levels: Level[] = [];
|
|
81
168
|
const pkgs = new Set<string>();
|
|
82
169
|
let payloadOnly = false;
|
|
83
|
-
for (const { message, files } of commits) {
|
|
170
|
+
for (const { hash, message, files } of commits) {
|
|
84
171
|
const subject = (message.split("\n")[0] ?? "").trim();
|
|
85
172
|
if (RELEASE_SYNC.test(subject)) continue;
|
|
86
173
|
const touched = files.filter((f) =>
|
|
87
|
-
RELEASE_PACKAGES.some((p) => f.startsWith(
|
|
174
|
+
RELEASE_PACKAGES.some((p) => payloadPaths(p).some((prefix) => f.startsWith(prefix))),
|
|
88
175
|
);
|
|
89
|
-
|
|
176
|
+
// A lockfile edit counts only for packages whose inlined deps moved.
|
|
177
|
+
const relocked = files.includes(LOCKFILE) ? lockChangedPackages(root, `${hash}^`, hash) : [];
|
|
178
|
+
if (touched.length === 0 && relocked.length === 0) continue;
|
|
90
179
|
const lvl = subjectLevel(message);
|
|
91
180
|
if (lvl) levels.push(lvl);
|
|
92
181
|
else if (!subject.startsWith("Merge ")) payloadOnly = true;
|
|
93
|
-
for (const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
182
|
+
for (const pkg of RELEASE_PACKAGES)
|
|
183
|
+
if (
|
|
184
|
+
relocked.includes(pkg) ||
|
|
185
|
+
touched.some((f) => payloadPaths(pkg).some((prefix) => f.startsWith(prefix)))
|
|
186
|
+
)
|
|
187
|
+
pkgs.add(pkg);
|
|
97
188
|
}
|
|
98
189
|
if (levels.length === 0) return { level: payloadOnly ? "patch" : null, productPkgs: [...pkgs] };
|
|
99
190
|
const level = levels.reduce<Level>(
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"workit-mcp": [
|
|
3
|
+
"@modelcontextprotocol/sdk",
|
|
4
|
+
"@openclaw/fs-safe",
|
|
5
|
+
"ajv",
|
|
6
|
+
"ajv-formats",
|
|
7
|
+
"fast-deep-equal",
|
|
8
|
+
"fast-uri",
|
|
9
|
+
"json-schema-traverse",
|
|
10
|
+
"zod",
|
|
11
|
+
"zod-to-json-schema"
|
|
12
|
+
],
|
|
13
|
+
"workit-cli": [
|
|
14
|
+
"@alcalzone/ansi-tokenize",
|
|
15
|
+
"@inkjs/ui",
|
|
16
|
+
"@openclaw/fs-safe",
|
|
17
|
+
"ansi-escapes",
|
|
18
|
+
"ansi-regex",
|
|
19
|
+
"ansi-styles",
|
|
20
|
+
"auto-bind",
|
|
21
|
+
"chalk",
|
|
22
|
+
"cli-boxes",
|
|
23
|
+
"cli-cursor",
|
|
24
|
+
"cli-truncate",
|
|
25
|
+
"code-excerpt",
|
|
26
|
+
"convert-to-spaces",
|
|
27
|
+
"deepmerge",
|
|
28
|
+
"environment",
|
|
29
|
+
"es-toolkit",
|
|
30
|
+
"escape-string-regexp",
|
|
31
|
+
"figures",
|
|
32
|
+
"get-east-asian-width",
|
|
33
|
+
"indent-string",
|
|
34
|
+
"ink",
|
|
35
|
+
"is-fullwidth-code-point",
|
|
36
|
+
"is-in-ci",
|
|
37
|
+
"is-unicode-supported",
|
|
38
|
+
"patch-console",
|
|
39
|
+
"react",
|
|
40
|
+
"react-devtools-core",
|
|
41
|
+
"react-reconciler",
|
|
42
|
+
"restore-cursor",
|
|
43
|
+
"restore-cursor/onetime",
|
|
44
|
+
"restore-cursor/onetime/mimic-fn",
|
|
45
|
+
"scheduler",
|
|
46
|
+
"signal-exit",
|
|
47
|
+
"slice-ansi",
|
|
48
|
+
"stack-utils",
|
|
49
|
+
"string-width",
|
|
50
|
+
"strip-ansi",
|
|
51
|
+
"terminal-size",
|
|
52
|
+
"widest-line",
|
|
53
|
+
"wrap-ansi",
|
|
54
|
+
"ws",
|
|
55
|
+
"yoga-layout",
|
|
56
|
+
"zod"
|
|
57
|
+
],
|
|
58
|
+
"workit-opencode": [
|
|
59
|
+
"@openclaw/fs-safe",
|
|
60
|
+
"@opencode-ai/plugin",
|
|
61
|
+
"@opencode-ai/plugin/zod",
|
|
62
|
+
"@opencode/plugin",
|
|
63
|
+
"zod"
|
|
64
|
+
],
|
|
65
|
+
"workit-cursor": [
|
|
66
|
+
"@modelcontextprotocol/sdk",
|
|
67
|
+
"@openclaw/fs-safe",
|
|
68
|
+
"ajv",
|
|
69
|
+
"ajv-formats",
|
|
70
|
+
"fast-deep-equal",
|
|
71
|
+
"fast-uri",
|
|
72
|
+
"json-schema-traverse",
|
|
73
|
+
"zod",
|
|
74
|
+
"zod-to-json-schema"
|
|
75
|
+
],
|
|
76
|
+
"workit-codex": [
|
|
77
|
+
"@modelcontextprotocol/sdk",
|
|
78
|
+
"@openclaw/fs-safe",
|
|
79
|
+
"ajv",
|
|
80
|
+
"ajv-formats",
|
|
81
|
+
"fast-deep-equal",
|
|
82
|
+
"fast-uri",
|
|
83
|
+
"json-schema-traverse",
|
|
84
|
+
"zod",
|
|
85
|
+
"zod-to-json-schema"
|
|
86
|
+
],
|
|
87
|
+
"workit-pi": ["@openclaw/fs-safe", "zod"],
|
|
88
|
+
"workit-claude-code": [
|
|
89
|
+
"@alcalzone/ansi-tokenize",
|
|
90
|
+
"@inkjs/ui",
|
|
91
|
+
"@openclaw/fs-safe",
|
|
92
|
+
"ansi-escapes",
|
|
93
|
+
"ansi-regex",
|
|
94
|
+
"ansi-styles",
|
|
95
|
+
"auto-bind",
|
|
96
|
+
"chalk",
|
|
97
|
+
"cli-boxes",
|
|
98
|
+
"cli-cursor",
|
|
99
|
+
"cli-truncate",
|
|
100
|
+
"code-excerpt",
|
|
101
|
+
"convert-to-spaces",
|
|
102
|
+
"deepmerge",
|
|
103
|
+
"environment",
|
|
104
|
+
"es-toolkit",
|
|
105
|
+
"escape-string-regexp",
|
|
106
|
+
"figures",
|
|
107
|
+
"get-east-asian-width",
|
|
108
|
+
"indent-string",
|
|
109
|
+
"ink",
|
|
110
|
+
"is-fullwidth-code-point",
|
|
111
|
+
"is-in-ci",
|
|
112
|
+
"is-unicode-supported",
|
|
113
|
+
"patch-console",
|
|
114
|
+
"react",
|
|
115
|
+
"react-devtools-core",
|
|
116
|
+
"react-reconciler",
|
|
117
|
+
"restore-cursor",
|
|
118
|
+
"restore-cursor/onetime",
|
|
119
|
+
"restore-cursor/onetime/mimic-fn",
|
|
120
|
+
"scheduler",
|
|
121
|
+
"signal-exit",
|
|
122
|
+
"slice-ansi",
|
|
123
|
+
"stack-utils",
|
|
124
|
+
"string-width",
|
|
125
|
+
"strip-ansi",
|
|
126
|
+
"terminal-size",
|
|
127
|
+
"widest-line",
|
|
128
|
+
"wrap-ansi",
|
|
129
|
+
"ws",
|
|
130
|
+
"yoga-layout",
|
|
131
|
+
"zod"
|
|
132
|
+
]
|
|
133
|
+
}
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
// package so release logs answer "what shipped?" without leaving the terminal.
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
latestTag,
|
|
9
|
+
lockChangedPackages,
|
|
10
|
+
payloadPaths,
|
|
11
|
+
RELEASE_PACKAGES,
|
|
12
|
+
} from "./analyze-release-scope";
|
|
8
13
|
|
|
9
14
|
const git = (root: string, args: string[]): string =>
|
|
10
15
|
execFileSync("git", args, { cwd: root, encoding: "utf8" }).trim();
|
|
@@ -12,8 +17,13 @@ const git = (root: string, args: string[]): string =>
|
|
|
12
17
|
export function changedPackages(root: string, fromTag: string): string[] {
|
|
13
18
|
// Committed state only: <tag>..HEAD, never the working tree — unreviewed
|
|
14
19
|
// local edits must not decide what ships.
|
|
20
|
+
// Plus packages whose inlined third-party deps resolve differently in the
|
|
21
|
+
// lockfile (BUNDLED_DEPS); a dev-tooling-only lockfile bump changes none.
|
|
22
|
+
const relocked = new Set<string>(lockChangedPackages(root, fromTag, "HEAD"));
|
|
15
23
|
return RELEASE_PACKAGES.filter((pkg) => {
|
|
16
|
-
|
|
24
|
+
if (relocked.has(pkg)) return true;
|
|
25
|
+
// Own directory plus any sources bundled into its dist/ (BUNDLED_SOURCES).
|
|
26
|
+
const out = git(root, ["diff", "--name-only", `${fromTag}..HEAD`, "--", ...payloadPaths(pkg)]);
|
|
17
27
|
return out !== "";
|
|
18
28
|
});
|
|
19
29
|
}
|
|
@@ -38,20 +48,15 @@ export function publishChanged(opts: {
|
|
|
38
48
|
execFileSync(cmd, args, { cwd: o.cwd, encoding: "utf8", stdio: "inherit" }));
|
|
39
49
|
const tag =
|
|
40
50
|
opts.fromTag !== undefined ? (opts.fromTag === "" ? null : opts.fromTag) : latestTag(root);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (const pkg of RELEASE_PACKAGES) {
|
|
45
|
-
const cwd = resolve(root, "packages", pkg);
|
|
46
|
-
if (!dryRun) run("npm", ["publish", "--access", "public"], { cwd });
|
|
47
|
-
published.push(pkg);
|
|
48
|
-
console.log(`published ${pkg} @ ${cwd}`);
|
|
49
|
-
}
|
|
50
|
-
return { published, skipped: [], tag: null };
|
|
51
|
-
}
|
|
52
|
-
const changed = new Set(changedPackages(root, tag));
|
|
51
|
+
// First-ever release: everything ships.
|
|
52
|
+
const changed =
|
|
53
|
+
tag === null ? new Set<string>(RELEASE_PACKAGES) : new Set(changedPackages(root, tag));
|
|
53
54
|
const published: string[] = [];
|
|
54
55
|
const skipped: string[] = [];
|
|
56
|
+
const failed: Array<{ pkg: string; error: string }> = [];
|
|
57
|
+
// Every changed package is attempted even after a failure: one package that
|
|
58
|
+
// cannot publish (e.g. a first publish of a new npm name the token does not
|
|
59
|
+
// cover) must not leave the others unreleased. Failures throw at the end.
|
|
55
60
|
for (const pkg of RELEASE_PACKAGES) {
|
|
56
61
|
if (!changed.has(pkg)) {
|
|
57
62
|
skipped.push(pkg);
|
|
@@ -62,12 +67,19 @@ export function publishChanged(opts: {
|
|
|
62
67
|
try {
|
|
63
68
|
if (!dryRun) run("npm", ["publish", "--access", "public"], { cwd });
|
|
64
69
|
} catch (e) {
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
71
|
+
failed.push({ pkg, error });
|
|
72
|
+
console.log(`publish failed ${pkg}: ${error}`);
|
|
73
|
+
continue;
|
|
67
74
|
}
|
|
68
75
|
published.push(pkg);
|
|
69
76
|
console.log(`published ${pkg} @ ${cwd}`);
|
|
70
77
|
}
|
|
78
|
+
if (failed.length > 0) {
|
|
79
|
+
const summary = `publish failed for ${failed.length} package(s): ${failed.map((f) => f.pkg).join(", ")}; published: ${published.join(", ") || "none"}. A first publish of a new @brainervirus package needs an npm token allowed to create packages in the scope.`;
|
|
80
|
+
console.log(summary);
|
|
81
|
+
throw new Error(summary);
|
|
82
|
+
}
|
|
71
83
|
return { published, skipped, tag };
|
|
72
84
|
}
|
|
73
85
|
|
|
@@ -25,14 +25,19 @@ for (const pkg of [
|
|
|
25
25
|
"workit-cursor",
|
|
26
26
|
"workit-codex",
|
|
27
27
|
"workit-pi",
|
|
28
|
+
"workit-claude-code",
|
|
28
29
|
]) {
|
|
29
30
|
const file = resolve(root, `packages/${pkg}/package.json`);
|
|
30
31
|
const data = JSON.parse(readFileSync(file, "utf8"));
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
// devDependencies too: the Claude Code plugin bundles core and the CLI into
|
|
33
|
+
// dist/, so they are build-time only, but a published manifest must still
|
|
34
|
+
// never carry workspace:*.
|
|
35
|
+
const groups = [data.dependencies, data.devDependencies].filter(Boolean);
|
|
36
|
+
if (groups.length === 0) continue;
|
|
37
|
+
for (const deps of groups)
|
|
38
|
+
for (const name of Object.keys(deps)) {
|
|
39
|
+
if (name.startsWith("@brainervirus/")) deps[name] = `^${core.version}`;
|
|
40
|
+
}
|
|
36
41
|
writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
|
|
37
42
|
}
|
|
38
43
|
// The root marketplace index carries no release version; only the package
|
|
@@ -40,6 +45,7 @@ for (const pkg of [
|
|
|
40
45
|
for (const file of [
|
|
41
46
|
resolve(root, "packages/workit-cursor/.cursor-plugin/plugin.json"),
|
|
42
47
|
resolve(root, "packages/workit-codex/.codex-plugin/plugin.json"),
|
|
48
|
+
resolve(root, "packages/workit-claude-code/.claude-plugin/plugin.json"),
|
|
43
49
|
]) {
|
|
44
50
|
const data = JSON.parse(readFileSync(file, "utf8"));
|
|
45
51
|
data.version = core.version;
|
|
@@ -9,7 +9,7 @@ import { execFileSync } from "node:child_process";
|
|
|
9
9
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
11
|
|
|
12
|
-
const SYNC_MANIFEST_PATHS = [
|
|
12
|
+
export const SYNC_MANIFEST_PATHS = [
|
|
13
13
|
"package.json",
|
|
14
14
|
"packages/workit-core/package.json",
|
|
15
15
|
"packages/workit-mcp/package.json",
|
|
@@ -18,9 +18,13 @@ const SYNC_MANIFEST_PATHS = [
|
|
|
18
18
|
"packages/workit-cursor/package.json",
|
|
19
19
|
"packages/workit-codex/package.json",
|
|
20
20
|
"packages/workit-pi/package.json",
|
|
21
|
+
"packages/workit-claude-code/package.json",
|
|
21
22
|
// Kept in lockstep with packages/workit-core/package.json by contract test.
|
|
22
23
|
"packages/workit-cursor/.cursor-plugin/plugin.json",
|
|
23
24
|
"packages/workit-codex/.codex-plugin/plugin.json",
|
|
25
|
+
// Claude Code reads the plugin version from here (the marketplace entry
|
|
26
|
+
// carries none), so it must track the published npm version.
|
|
27
|
+
"packages/workit-claude-code/.claude-plugin/plugin.json",
|
|
24
28
|
];
|
|
25
29
|
|
|
26
30
|
export type ManifestSyncResult = { version: string; changed: string[] };
|
|
@@ -16,12 +16,16 @@ Use this method when assessment selects the `testing` dimension.
|
|
|
16
16
|
2. State one behavior and its observable result. Choose the narrowest stable
|
|
17
17
|
boundary a caller or user depends on; avoid private helpers and incidental
|
|
18
18
|
representations.
|
|
19
|
-
3. Write one vertical RED slice that fails for the missing behavior
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
3. Write one vertical RED slice that fails for the missing behavior and run it
|
|
20
|
+
through the CLI so the failure is observed: `workit check test` (the repo's
|
|
21
|
+
configured `test` check; `npx -y @brainervirus/workit-cli check test` when
|
|
22
|
+
`workit` is not on PATH). Implement the smallest change, then run the same
|
|
23
|
+
check GREEN. Close accepts only a fresh passing run of the configured check
|
|
24
|
+
that `workit check` observed; a recorded "tests pass" is a note, and an
|
|
25
|
+
ad-hoc `workit check -- <cmd>` never satisfies the gate. If no `test` check
|
|
26
|
+
is configured or detected, add `workit.checks.json` (committed) with it.
|
|
27
|
+
4. Add only another slice for a distinct behavior or risk. Any edit makes the
|
|
28
|
+
observed check stale: re-run `workit check test` before closing.
|
|
25
29
|
|
|
26
30
|
## Reject noisy tests
|
|
27
31
|
|
|
@@ -36,8 +40,10 @@ Use this method when assessment selects the `testing` dimension.
|
|
|
36
40
|
type-only or CSS-class coupling. If the test still passes when every
|
|
37
41
|
imported function returns undefined, rewrite the assertion or delete it.
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
Run RED/GREEN through `workit check`, which records the observed result on the
|
|
44
|
+
current task; shared `evidence` operations are for notes and non-test evidence.
|
|
45
|
+
Do not add a second lifecycle, approval chain, or test workflow outside the
|
|
46
|
+
current task state.
|
|
41
47
|
|
|
42
48
|
## Common mistakes
|
|
43
49
|
|
|
@@ -45,4 +51,4 @@ lifecycle, approval chain, or test workflow outside the current task state.
|
|
|
45
51
|
| --- | --- |
|
|
46
52
|
| "The pin changed, so assert the new string" | Exercise the affected consumer behavior. |
|
|
47
53
|
| "The code is obvious" | A small vertical slice still proves the contract. |
|
|
48
|
-
| Keeping a passing test after the boundary moved |
|
|
54
|
+
| Keeping a passing test after the boundary moved | Re-run `workit check test` on the current tree. |
|
|
@@ -19,7 +19,8 @@ established root cause.
|
|
|
19
19
|
inferences, and unknowns with references; trace the failing value and all
|
|
20
20
|
relevant callers before editing.
|
|
21
21
|
3. State the root-cause hypothesis and the smallest in-scope fix. Write a focused
|
|
22
|
-
regression at the boundary when practical, then run RED and GREEN
|
|
22
|
+
regression at the boundary when practical, then run RED and GREEN through
|
|
23
|
+
`workit check <name>` so the results are observed, not reported.
|
|
23
24
|
4. Acquire writer authority through `writer` before mutation. Reconcile the
|
|
24
25
|
candidate, evidence, and findings after the change; investigate sibling paths
|
|
25
26
|
and stale conclusions rather than assuming the first patch worked.
|