@workit-dev/core 0.0.1 → 1.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/LICENSE +21 -0
- package/README.md +22 -43
- package/package.json +56 -6
- package/schemas/config.json +74 -0
- package/schemas/vcs.json +79 -0
- package/schemas/workspaces.json +1029 -0
- package/schemas/youtrack.json +84 -0
- package/scripts/analyze-release-scope.ts +215 -0
- package/scripts/bundled-deps.json +131 -0
- package/scripts/fixtures/sample-plan.md +15 -0
- package/scripts/fixtures/sample-sdd/progress.md +2 -0
- package/scripts/install-codex-plugin.sh +28 -0
- package/scripts/install-cursor-plugin.sh +146 -0
- package/scripts/install-opencode-plugin.sh +79 -0
- package/scripts/install-pi-package.sh +56 -0
- package/scripts/lib/config-dir.sh +28 -0
- package/scripts/publish-changed-packages.ts +395 -0
- package/scripts/rewrite-workspace-deps.ts +53 -0
- package/scripts/run-cursor-mcp.sh +6 -0
- package/scripts/sync-release-manifests.ts +82 -0
- package/scripts/sync-runtime.sh +142 -0
- package/scripts/validate-cursor-marketplace.ts +171 -0
- package/scripts/verify-bundle-sources.ts +134 -0
- package/scripts/write-schemas.ts +16 -0
- package/skills/workit-architecture/SKILL.md +72 -0
- package/skills/workit-architecture/agents/openai.yaml +3 -0
- package/skills/workit-architecture/references/instruction-files.md +52 -0
- package/skills/workit-architecture/references/vocabulary.md +53 -0
- package/skills/workit-bdd/SKILL.md +53 -0
- package/skills/workit-continue/SKILL.md +54 -0
- package/skills/workit-debug/SKILL.md +45 -0
- package/skills/workit-deslop/SKILL.md +58 -0
- package/skills/workit-fanout/SKILL.md +71 -0
- package/skills/workit-fanout/references/brief.md +141 -0
- package/skills/workit-fanout/references/orchestration.md +169 -0
- package/skills/workit-implement/SKILL.md +71 -0
- package/skills/workit-retro/SKILL.md +72 -0
- package/skills/workit-retro/agents/openai.yaml +3 -0
- package/skills/workit-retro/references/sources.md +35 -0
- package/skills/workit-retro/references/steering.md +39 -0
- package/skills/workit-review/SKILL.md +66 -0
- package/skills/workit-review/references/impact.md +24 -0
- package/skills/workit-shape/SKILL.md +71 -0
- package/skills/workit-shape/references/diagrams.md +17 -0
- package/skills/workit-shape/references/knowledge.md +61 -0
- package/skills/workit-shape/references/mockups.md +15 -0
- package/skills/workit-shape/references/slicing.md +44 -0
- package/skills/workit-ship/SKILL.md +71 -0
- package/skills/workit-ship/references/changelog.md +44 -0
- package/skills/workit-ship/references/pr-body.md +32 -0
- package/skills/workit-ship/references/release.md +39 -0
- package/skills/workit-test-audit/SKILL.md +54 -0
- package/skills/workit-verify-app/SKILL.md +68 -0
- package/skills/workit-verify-app/references/template.md +51 -0
- package/src/autonomy.ts +910 -0
- package/src/check-config.ts +613 -0
- package/src/checks.ts +534 -0
- package/src/ci-waiter.ts +159 -0
- package/src/core/boundary.ts +55 -0
- package/src/core/branch-policy.ts +112 -0
- package/src/core/branch.ts +1192 -0
- package/src/core/changelog-gate.ts +349 -0
- package/src/core/changelog-merge.ts +214 -0
- package/src/core/changelog.ts +392 -0
- package/src/core/cleanup.ts +638 -0
- package/src/core/cli-locate.ts +173 -0
- package/src/core/commit-flavors.ts +65 -0
- package/src/core/config-schemas.ts +653 -0
- package/src/core/config.ts +351 -0
- package/src/core/context-read.ts +144 -0
- package/src/core/docs-gate.ts +69 -0
- package/src/core/finding-evidence.ts +89 -0
- package/src/core/finding-queue.ts +36 -0
- package/src/core/git.ts +73 -0
- package/src/core/gitignore.ts +52 -0
- package/src/core/heavy-eta.ts +170 -0
- package/src/core/heavy-lease.ts +290 -0
- package/src/core/heavy-rules.ts +373 -0
- package/src/core/heavy-scope.ts +131 -0
- package/src/core/heavy-words.ts +83 -0
- package/src/core/heavy.ts +535 -0
- package/src/core/hygiene.ts +149 -0
- package/src/core/init.ts +218 -0
- package/src/core/live-session.ts +333 -0
- package/src/core/logger.ts +310 -0
- package/src/core/methods.ts +185 -0
- package/src/core/model-fallback.ts +208 -0
- package/src/core/node-resolve.mjs +249 -0
- package/src/core/operation-input.ts +632 -0
- package/src/core/package-root.ts +28 -0
- package/src/core/policy/attributes.ts +97 -0
- package/src/core/policy/bookkeeping.ts +119 -0
- package/src/core/policy/carry.ts +122 -0
- package/src/core/policy/check-evidence.ts +115 -0
- package/src/core/policy/current-judgment.ts +20 -0
- package/src/core/policy/derive.ts +387 -0
- package/src/core/policy/judgment.ts +246 -0
- package/src/core/policy/lane.ts +562 -0
- package/src/core/policy/pr-lane.ts +32 -0
- package/src/core/policy/substance.ts +365 -0
- package/src/core/policy-resolver.ts +94 -0
- package/src/core/pr-create.ts +448 -0
- package/src/core/pr-template.ts +162 -0
- package/src/core/release-notes.ts +197 -0
- package/src/core/release-tracks.ts +779 -0
- package/src/core/remotes.ts +436 -0
- package/src/core/repo-context.ts +542 -0
- package/src/core/repo-paths.ts +29 -0
- package/src/core/resources.ts +1299 -0
- package/src/core/role-models.ts +603 -0
- package/src/core/route-intent.ts +305 -0
- package/src/core/rules.ts +65 -0
- package/src/core/runtime-identity.ts +63 -0
- package/src/core/safe-write.ts +87 -0
- package/src/core/scope-glob.ts +56 -0
- package/src/core/scripts.ts +6 -0
- package/src/core/skill-manifests.ts +173 -0
- package/src/core/store-lock.ts +374 -0
- package/src/core/support-matrix.ts +24 -0
- package/src/core/task-context.ts +247 -0
- package/src/core/task-contract.ts +1279 -0
- package/src/core/task-delivery.ts +109 -0
- package/src/core/task-engine.ts +2248 -0
- package/src/core/task-evaluation.ts +1147 -0
- package/src/core/task-store.ts +2649 -0
- package/src/core/task-view.ts +180 -0
- package/src/core/templates.ts +22 -0
- package/src/core/tracker-issues.ts +174 -0
- package/src/core/vcs-config.ts +540 -0
- package/src/core/workers.ts +62 -0
- package/src/core/workit-paths.mjs +242 -0
- package/src/core/workspace-branch-policy.ts +123 -0
- package/src/core/workspaces.ts +1745 -0
- package/src/core/youtrack-backend.ts +288 -0
- package/src/core/youtrack-tools.ts +260 -0
- package/src/core/youtrack-writes.ts +73 -0
- package/src/core/youtrack-ytrack.ts +217 -0
- package/src/core/youtrack.ts +454 -0
- package/src/core/ytrack-skill.ts +297 -0
- package/src/core.ts +171 -0
- package/src/deslop/lex.ts +271 -0
- package/src/deslop/scan.ts +232 -0
- package/src/duration.ts +27 -0
- package/src/fanout-brief.ts +486 -0
- package/src/fanout-check.ts +455 -0
- package/src/fanout-landed.ts +370 -0
- package/src/fanout-stack.ts +108 -0
- package/src/fanout-status.ts +362 -0
- package/src/fanout-worktree.ts +893 -0
- package/src/fanout.ts +1473 -0
- package/src/forge/exec.ts +385 -0
- package/src/forge/github.ts +831 -0
- package/src/forge/gitlab.ts +906 -0
- package/src/forge/issue-forge.ts +209 -0
- package/src/forge/issue-link.ts +468 -0
- package/src/forge/issue-ops.ts +314 -0
- package/src/forge/merge-gate-base-red.ts +72 -0
- package/src/forge/pr-body.ts +100 -0
- package/src/forge/pr-edit.ts +292 -0
- package/src/forge/pr-ops.ts +1109 -0
- package/src/forge/pr-status.ts +10 -0
- package/src/forge/redact.ts +195 -0
- package/src/forge/report.ts +994 -0
- package/src/forge/reruns.ts +132 -0
- package/src/forge/resolve.ts +509 -0
- package/src/forge/types.ts +242 -0
- package/src/forge/verify.ts +398 -0
- package/src/git/default-base.ts +130 -0
- package/src/git/env-redirect.ts +28 -0
- package/src/git/facts.ts +122 -0
- package/src/git/ops.ts +1823 -0
- package/src/git/refs-read.ts +332 -0
- package/src/git/release.ts +1020 -0
- package/src/git/rev.ts +1175 -0
- package/src/git/session-hook.ts +597 -0
- package/src/git/stage.ts +63 -0
- package/src/hooks/agent-model.ts +149 -0
- package/src/hooks/budget-signal.ts +284 -0
- package/src/hooks/context.ts +461 -0
- package/src/hooks/cwd-normalize.ts +79 -0
- package/src/hooks/deadline.ts +228 -0
- package/src/hooks/delivery-verbs.ts +182 -0
- package/src/hooks/descriptor.ts +124 -0
- package/src/hooks/handle.ts +433 -0
- package/src/hooks/heavy-gate.ts +99 -0
- package/src/hooks/hook-runs.ts +130 -0
- package/src/hooks/hosts/claude-code.ts +380 -0
- package/src/hooks/hosts/codex.ts +372 -0
- package/src/hooks/hosts/cursor.ts +419 -0
- package/src/hooks/hosts/fields.ts +98 -0
- package/src/hooks/hosts/opencode.ts +79 -0
- package/src/hooks/hosts/pi.ts +78 -0
- package/src/hooks/index.ts +60 -0
- package/src/hooks/model-limit.ts +378 -0
- package/src/hooks/model-switch.ts +35 -0
- package/src/hooks/policy.ts +29 -0
- package/src/hooks/process.ts +48 -0
- package/src/hooks/protocol.ts +135 -0
- package/src/hooks/raw-git.ts +2289 -0
- package/src/hooks/repeat-failure.ts +115 -0
- package/src/hooks/run.ts +54 -0
- package/src/hooks/session-marker.ts +151 -0
- package/src/hooks/shell-words.ts +532 -0
- package/src/hooks/skill-nudge.ts +295 -0
- package/src/hooks/spawn-gate.ts +72 -0
- package/src/hooks/standing-signal.ts +99 -0
- package/src/hooks/standing.ts +225 -0
- package/src/hooks/steering.ts +74 -0
- package/src/hooks/stop.ts +408 -0
- package/src/hooks/worktree-confine.ts +415 -0
- package/src/hooks/write-gate.ts +1174 -0
- package/src/host-session.ts +459 -0
- package/src/knowledge.ts +368 -0
- package/src/ledger.ts +2913 -0
- package/src/stack.ts +2570 -0
- package/src/store/event-log.ts +217 -0
- package/src/store/notes.ts +29 -0
- package/src/store/patch.ts +135 -0
- package/src/store/paths.ts +368 -0
- package/src/store/reduce.ts +203 -0
- package/src/test-audit-mocks.ts +309 -0
- package/src/verdict-cover.ts +151 -0
- package/src/worktree-deps.ts +142 -0
- package/templates/headers.md +3 -0
- package/templates/hygiene/.editorconfig +8 -0
- package/templates/hygiene/.gitattributes +3 -0
- package/templates/hygiene/CHANGELOG.md +14 -0
- package/templates/hygiene/CONTRIBUTING.md +3 -0
- package/templates/hygiene/LICENSE +21 -0
- package/templates/hygiene/README.md +3 -0
- package/templates/issue-update.md +5 -0
- package/templates/plan-template.md +26 -0
- package/templates/spec-template.md +64 -0
- package/templates/workit-contract.md +13 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Optional manual manifest sync. The release never runs it: since #704
|
|
3
|
+
// semantic-release and rewrite-workspace-deps.ts stamp the version into the
|
|
4
|
+
// manifests of the CI checkout at publish time, and main is not synced, so the
|
|
5
|
+
// committed manifests keep the version of the last manual sync. Run it in any
|
|
6
|
+
// PR to align every tracked manifest with the latest `workit-dev-v*` tag (or a version
|
|
7
|
+
// given as argv[2]); it is idempotent. plugin.test.ts reads
|
|
8
|
+
// SYNC_MANIFEST_PATHS to keep every plugin manifest on this list.
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { RELEASE_TAG, TAG_GLOB } from "./analyze-release-scope";
|
|
13
|
+
|
|
14
|
+
export const SYNC_MANIFEST_PATHS = [
|
|
15
|
+
"package.json",
|
|
16
|
+
"packages/workit-core/package.json",
|
|
17
|
+
"packages/workit-mcp/package.json",
|
|
18
|
+
"packages/workit-cli/package.json",
|
|
19
|
+
"packages/workit-opencode/package.json",
|
|
20
|
+
"packages/workit-cursor/package.json",
|
|
21
|
+
"packages/workit-codex/package.json",
|
|
22
|
+
"packages/workit-pi/package.json",
|
|
23
|
+
"packages/workit-claude-code/package.json",
|
|
24
|
+
// Kept in lockstep with packages/workit-core/package.json by contract test.
|
|
25
|
+
"packages/workit-cursor/.cursor-plugin/plugin.json",
|
|
26
|
+
"packages/workit-codex/.codex-plugin/plugin.json",
|
|
27
|
+
// Claude Code reads the plugin version from here (the marketplace entry
|
|
28
|
+
// carries none), so it must track the published npm version.
|
|
29
|
+
"packages/workit-claude-code/.claude-plugin/plugin.json",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export type ManifestSyncResult = { version: string; changed: string[] };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Write `version` (a bare semver or a `workit-dev-v` release tag) into every tracked
|
|
36
|
+
* manifest under `root`. Idempotent: manifests already at the target version
|
|
37
|
+
* are left byte-untouched and omitted from `changed`. Throws on a value that
|
|
38
|
+
* is not a plain release version (`latest`, branches, ranges).
|
|
39
|
+
*/
|
|
40
|
+
export function syncManifests(root: string, tagOrVersion: string): ManifestSyncResult {
|
|
41
|
+
const value = tagOrVersion.trim();
|
|
42
|
+
const version = RELEASE_TAG.exec(value)?.[1] ?? RELEASE_TAG.exec(`workit-dev-v${value}`)?.[1];
|
|
43
|
+
if (!version) throw new Error(`invalid version tag: ${JSON.stringify(tagOrVersion)}`);
|
|
44
|
+
const changed: string[] = [];
|
|
45
|
+
for (const rel of SYNC_MANIFEST_PATHS) {
|
|
46
|
+
const file = resolve(root, ...rel.split("/"));
|
|
47
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
48
|
+
if (parsed.version === version) continue;
|
|
49
|
+
parsed.version = version;
|
|
50
|
+
writeFileSync(file, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
|
|
51
|
+
changed.push(rel);
|
|
52
|
+
}
|
|
53
|
+
return { version, changed };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The newest release tag by descending semver refname order (none throws). */
|
|
57
|
+
function latestReleaseTag(cwd?: string): string {
|
|
58
|
+
const tag = execFileSync("git", ["tag", "--list", TAG_GLOB, "--sort=-v:refname"], {
|
|
59
|
+
cwd,
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
62
|
+
})
|
|
63
|
+
.split("\n")
|
|
64
|
+
.map((line) => line.trim())
|
|
65
|
+
.find((line) => RELEASE_TAG.test(line));
|
|
66
|
+
if (!tag)
|
|
67
|
+
throw new Error(`no ${TAG_GLOB} release tag found — run after the first semantic-release`);
|
|
68
|
+
return tag;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (import.meta.main) {
|
|
72
|
+
const root = process.argv[3]
|
|
73
|
+
? resolve(process.argv[3])
|
|
74
|
+
: resolve(import.meta.dir, "..", "..", "..");
|
|
75
|
+
const tag = process.argv[2] ?? latestReleaseTag(root);
|
|
76
|
+
const { version, changed } = syncManifests(root, tag);
|
|
77
|
+
console.log(
|
|
78
|
+
changed.length > 0
|
|
79
|
+
? `synced ${changed.length} manifest(s) to ${version}: ${changed.join(", ")}`
|
|
80
|
+
: `manifests already at ${version}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Sync workit runtime for Cursor + OpenCode.
|
|
3
|
+
# Prefer local monorepo (dev) when present; otherwise git-pull the share clone.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
7
|
+
. "$SCRIPT_DIR/lib/config-dir.sh"
|
|
8
|
+
|
|
9
|
+
SHARE="$(workit_data_dir)"
|
|
10
|
+
PLUGIN_DIR="${HOME}/.cursor/plugins/local/workit"
|
|
11
|
+
DEV_DEFAULT="${HOME}/Documents/projects/personal/workit"
|
|
12
|
+
DEV="${WORKIT_DEV:-$DEV_DEFAULT}"
|
|
13
|
+
REPO_SLUG="${WORKIT_REPO:-BrainerVirus/workit}"
|
|
14
|
+
LOCK_DIR="$(workit_runtime_dir)"
|
|
15
|
+
LOCK="$LOCK_DIR/sync.lock"
|
|
16
|
+
|
|
17
|
+
# RR-05: missing required tools, an unacquirable lock, or a failed clone must
|
|
18
|
+
# never look like a successful sync.
|
|
19
|
+
if ! command -v flock >/dev/null 2>&1; then
|
|
20
|
+
echo "FATAL: sync-runtime requires flock (util-linux) — not found in PATH" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
mkdir -p "$LOCK_DIR"
|
|
24
|
+
exec 9>"$LOCK"
|
|
25
|
+
if ! flock -n 9; then
|
|
26
|
+
echo "sync-runtime: another process holds $LOCK — state unverified, failing" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
SRC=""
|
|
31
|
+
if [ -f "${DEV}/packages/workit-opencode/src/plugin.ts" ] && [ -d "${DEV}/packages/workit-cursor/.cursor-plugin" ]; then
|
|
32
|
+
SRC="$DEV"
|
|
33
|
+
elif [ -d "${SHARE}/.git" ]; then
|
|
34
|
+
# RR-05: a failed update of an existing share clone must not look like a
|
|
35
|
+
# successful sync — propagate fetch/pull failures to a FATAL nonzero exit.
|
|
36
|
+
if ! git -C "$SHARE" fetch --quiet origin; then
|
|
37
|
+
echo "FATAL: could not fetch updates for $SHARE" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
if ! git -C "$SHARE" pull --ff-only --quiet origin main; then
|
|
41
|
+
echo "FATAL: could not update $SHARE from origin/main" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
SRC="$SHARE"
|
|
45
|
+
elif [ ! -d "${SHARE}/packages/workit-core/src" ]; then
|
|
46
|
+
mkdir -p "$(dirname "$SHARE")"
|
|
47
|
+
if ! git clone --depth 1 "https://github.com/${REPO_SLUG}.git" "$SHARE"; then
|
|
48
|
+
echo "FATAL: could not clone https://github.com/${REPO_SLUG}.git into $SHARE" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
SRC="$SHARE"
|
|
52
|
+
else
|
|
53
|
+
SRC="$SHARE"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
CURSOR_SRC="$SRC/packages/workit-cursor"
|
|
57
|
+
BUN_BIN=""
|
|
58
|
+
if [ -n "${BUN:-}" ]; then
|
|
59
|
+
BUN_BIN="$BUN"
|
|
60
|
+
if [ ! -x "$BUN_BIN" ]; then
|
|
61
|
+
echo "FATAL: BUN is set but unusable: $BUN_BIN" >&2
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
elif [ -x "$HOME/.bun/bin/bun" ]; then
|
|
65
|
+
BUN_BIN="$HOME/.bun/bin/bun"
|
|
66
|
+
elif command -v bun >/dev/null 2>&1; then
|
|
67
|
+
BUN_BIN="$(command -v bun)"
|
|
68
|
+
else
|
|
69
|
+
echo "FATAL: Bun is required to build the Cursor adapter" >&2
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
for DEP in @workit-dev/core @workit-dev/mcp @modelcontextprotocol/sdk zod; do
|
|
74
|
+
if [ ! -e "$SRC/node_modules/$DEP" ]; then
|
|
75
|
+
if [ ! -f "$SRC/bun.lock" ]; then
|
|
76
|
+
echo "FATAL: dependency install requires $SRC/bun.lock" >&2
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
if ! (cd "$SRC" && "$BUN_BIN" install --frozen-lockfile); then
|
|
80
|
+
echo "FATAL: root dependency install failed in $SRC" >&2
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
break
|
|
84
|
+
fi
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
if ! (cd "$SRC" && PATH="$(dirname "$BUN_BIN"):$PATH" "$BUN_BIN" "$SRC/packages/workit-mcp/scripts/build.ts"); then
|
|
88
|
+
echo "FATAL: shared MCP build failed in $SRC" >&2
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
if ! (cd "$SRC" && PATH="$(dirname "$BUN_BIN"):$PATH" "$BUN_BIN" "$CURSOR_SRC/scripts/build.ts"); then
|
|
92
|
+
echo "FATAL: Cursor adapter build failed in $CURSOR_SRC" >&2
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
for ENTRY in mcp-server.js cursor-session-start.js workit-hook.js; do
|
|
96
|
+
DIST_ENTRY="$CURSOR_SRC/dist/$ENTRY"
|
|
97
|
+
if [ ! -f "$DIST_ENTRY" ] || [ ! -s "$DIST_ENTRY" ] || [ "$(IFS= read -r LINE <"$DIST_ENTRY"; printf '%s' "$LINE")" != '#!/usr/bin/env node' ]; then
|
|
98
|
+
echo "FATAL: Cursor adapter invalid dist entry: $DIST_ENTRY" >&2
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
done
|
|
102
|
+
|
|
103
|
+
if [ "$SRC" != "$SHARE" ]; then
|
|
104
|
+
mkdir -p "$SHARE"
|
|
105
|
+
# Keep .git if share is a clone; never wipe it when syncing from the monorepo
|
|
106
|
+
# versions/, current and bin/ are the owned CLI install (workit upgrade): never delete them
|
|
107
|
+
rsync -a --delete \
|
|
108
|
+
--exclude '/versions' --exclude '/current' --exclude '/bin' \
|
|
109
|
+
--exclude '.git' \
|
|
110
|
+
--exclude 'node_modules' \
|
|
111
|
+
--exclude 'cursor/mcp/node_modules' \
|
|
112
|
+
--exclude '.cache' \
|
|
113
|
+
"$SRC/" "$SHARE/"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Cursor IDE package (real directory)
|
|
117
|
+
mkdir -p "${HOME}/.cursor/plugins/local"
|
|
118
|
+
mkdir -p "$PLUGIN_DIR"
|
|
119
|
+
rsync -a --delete \
|
|
120
|
+
--exclude 'mcp/node_modules' \
|
|
121
|
+
"$SHARE/packages/workit-cursor/" "$PLUGIN_DIR/"
|
|
122
|
+
|
|
123
|
+
if ! "$BUN_BIN" "$SHARE/packages/workit-core/src/core/skill-manifests.ts" "$PLUGIN_DIR"; then
|
|
124
|
+
echo "FATAL: Cursor skill validation failed: $PLUGIN_DIR" >&2
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
# Canonical user rules -> Cursor .mdc (compiled by the shared core)
|
|
128
|
+
CONFIG_RULES_DIR="$(resolve_config_dir)/rules"
|
|
129
|
+
if [ -d "$CONFIG_RULES_DIR" ]; then
|
|
130
|
+
# rules.ts is loaded only here (scripts/check-reachability.ts allowlists it):
|
|
131
|
+
# a failure must surface, never leave the Cursor rules silently stale.
|
|
132
|
+
if ! "$BUN_BIN" -e "
|
|
133
|
+
const { writeCompiledCursorRules } = await import('${SHARE}/packages/workit-core/src/core/rules.ts');
|
|
134
|
+
writeCompiledCursorRules('${PLUGIN_DIR}/rules');
|
|
135
|
+
" >/dev/null; then
|
|
136
|
+
echo "FATAL: Cursor rule compilation failed: $CONFIG_RULES_DIR -> $PLUGIN_DIR/rules" >&2
|
|
137
|
+
exit 1
|
|
138
|
+
fi
|
|
139
|
+
fi
|
|
140
|
+
printf '%s\n' "$SHARE/packages/workit-core" >"$PLUGIN_DIR/.workit-root"
|
|
141
|
+
|
|
142
|
+
exit 0
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Cursor Marketplace validator (Task 9, CA-13/CA-15/CA-17/CA-21): validates the
|
|
3
|
+
// tracked Marketplace artifact against the official Cursor JSON schemas plus the
|
|
4
|
+
// repository-specific invariants that a JSON Schema cannot express. Runs from a
|
|
5
|
+
// clean `git ls-files` checkout — it never reads dist/ or other ignored build
|
|
6
|
+
// output, so `bun run validate:cursor-marketplace` is a truthful clean-checkout
|
|
7
|
+
// gate for CI.
|
|
8
|
+
//
|
|
9
|
+
// Official schema snapshots (test/fixtures/cursor-schemas/) are verbatim copies
|
|
10
|
+
// of the current official schemas, retrieved 2026-08-13 from:
|
|
11
|
+
// - https://raw.githubusercontent.com/cursor/plugins/main/schemas/plugin.schema.json
|
|
12
|
+
// - https://raw.githubusercontent.com/cursor/plugins/main/schemas/marketplace.schema.json
|
|
13
|
+
//
|
|
14
|
+
// `source` path resolution (Task 7 carry-forward, RESOLVED): the official
|
|
15
|
+
// marketplace.schema.json documents `plugins[].source` as "Path to the plugin
|
|
16
|
+
// directory (relative to the marketplace root)". The marketplace root is the
|
|
17
|
+
// directory that CONTAINS `.cursor-plugin/marketplace.json` — the repo root —
|
|
18
|
+
// NOT `.cursor-plugin/` itself. Evidence: the official cursor/plugins repo keeps
|
|
19
|
+
// `.cursor-plugin/marketplace.json` at the repo root and lists sources such as
|
|
20
|
+
// `continual-learning` and `third_party/gmail` with no `../` prefix, even though
|
|
21
|
+
// `.cursor-plugin/` is a sibling of those directories. Therefore
|
|
22
|
+
// `source: "packages/workit-cursor"` is correct and kept verbatim.
|
|
23
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
import Ajv from "ajv";
|
|
27
|
+
import addFormats from "ajv-formats";
|
|
28
|
+
import { WORKIT_METHOD_SKILLS, validateSkillManifests } from "../src/core/skill-manifests";
|
|
29
|
+
|
|
30
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const repoRoot = path.resolve(scriptDir, "..", "..", "..");
|
|
32
|
+
const root = process.argv[2] ? path.resolve(process.argv[2]) : repoRoot;
|
|
33
|
+
|
|
34
|
+
const MARKETPLACE_REL = ".cursor-plugin/marketplace.json";
|
|
35
|
+
|
|
36
|
+
const frontmatterKeys = (file: string): Set<string> => {
|
|
37
|
+
const head = readFileSync(file, "utf8").slice(0, 4096);
|
|
38
|
+
const keys = new Set<string>();
|
|
39
|
+
if (!head.startsWith("---")) return keys;
|
|
40
|
+
const end = head.indexOf("\n---", 3);
|
|
41
|
+
if (end === -1) return keys;
|
|
42
|
+
for (const line of head.slice(3, end).split("\n")) {
|
|
43
|
+
const m = /^([A-Za-z0-9_-]+):/.exec(line);
|
|
44
|
+
if (m) keys.add(m[1]);
|
|
45
|
+
}
|
|
46
|
+
return keys;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const validateMarketplace = (rootArg: string): string[] => {
|
|
50
|
+
const errors: string[] = [];
|
|
51
|
+
|
|
52
|
+
if (!existsSync(path.join(rootArg, MARKETPLACE_REL))) {
|
|
53
|
+
errors.push(`missing ${MARKETPLACE_REL}`);
|
|
54
|
+
return errors;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 1. Official JSON Schema evaluation (AJV + ajv-formats), verbatim snapshots.
|
|
58
|
+
const validate = new Ajv({ strict: true, allErrors: true });
|
|
59
|
+
addFormats(validate);
|
|
60
|
+
const schemaDir = path.join(rootArg, "test/fixtures/cursor-schemas");
|
|
61
|
+
const pluginSchema = JSON.parse(readFileSync(path.join(schemaDir, "plugin.schema.json"), "utf8"));
|
|
62
|
+
const marketSchema = JSON.parse(
|
|
63
|
+
readFileSync(path.join(schemaDir, "marketplace.schema.json"), "utf8"),
|
|
64
|
+
);
|
|
65
|
+
const market = JSON.parse(readFileSync(path.join(rootArg, MARKETPLACE_REL), "utf8"));
|
|
66
|
+
if (!validate.validate(marketSchema, market)) {
|
|
67
|
+
errors.push(
|
|
68
|
+
`marketplace.json invalid: ${(validate.errors ?? []).map((e) => e.message).join("; ")}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Resolve each plugin source relative to the repo root; name must match.
|
|
73
|
+
for (const entry of (market.plugins ?? []) as { name: string; source: string }[]) {
|
|
74
|
+
const pluginDir = path.join(rootArg, entry.source);
|
|
75
|
+
if (!existsSync(path.join(pluginDir, ".cursor-plugin/plugin.json"))) {
|
|
76
|
+
errors.push(`plugin ${entry.name}: source ${entry.source} has no .cursor-plugin/plugin.json`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const plugin = JSON.parse(
|
|
80
|
+
readFileSync(path.join(pluginDir, ".cursor-plugin/plugin.json"), "utf8"),
|
|
81
|
+
);
|
|
82
|
+
if (!validate.validate(pluginSchema, plugin)) {
|
|
83
|
+
errors.push(
|
|
84
|
+
`plugin ${entry.name} plugin.json invalid: ${(validate.errors ?? []).map((e) => e.message).join("; ")}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (plugin.name !== entry.name) {
|
|
88
|
+
errors.push(`plugin ${entry.name}: plugin.json name ${plugin.name} does not match index`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 3. Component paths resolve inside the plugin root (no `..`, no absolute).
|
|
92
|
+
// `skills`/`rules` are path-like (string or string[]). `mcpServers`/`hooks`
|
|
93
|
+
// may also be inline objects or arrays of objects, which the JSON-schema
|
|
94
|
+
// check already validates — only string/string[] values get the
|
|
95
|
+
// path-escape/existence check; non-string forms are skipped gracefully.
|
|
96
|
+
for (const field of ["skills", "rules", "mcpServers", "hooks"] as const) {
|
|
97
|
+
const value = plugin[field];
|
|
98
|
+
const rels: string[] =
|
|
99
|
+
typeof value === "string"
|
|
100
|
+
? [value]
|
|
101
|
+
: Array.isArray(value)
|
|
102
|
+
? value.filter((v): v is string => typeof v === "string")
|
|
103
|
+
: [];
|
|
104
|
+
for (const rel of rels) {
|
|
105
|
+
if (rel.includes("..") || rel.startsWith("/")) {
|
|
106
|
+
errors.push(`plugin ${entry.name}: ${field} path escapes root: ${rel}`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!existsSync(path.join(pluginDir, rel))) {
|
|
110
|
+
errors.push(`plugin ${entry.name}: ${field} path missing: ${rel}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 4. Logo resolves inside the plugin root and exists.
|
|
116
|
+
const logo = plugin.logo as string | undefined;
|
|
117
|
+
if (logo && (logo.includes("..") || logo.startsWith("/"))) {
|
|
118
|
+
errors.push(`plugin ${entry.name}: logo path escapes root: ${logo}`);
|
|
119
|
+
} else if (logo && !existsSync(path.join(pluginDir, logo))) {
|
|
120
|
+
errors.push(`plugin ${entry.name}: logo missing: ${logo}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 5. Skills and rules carry valid frontmatter.
|
|
124
|
+
const skillRoots: [string, readonly string[]][] = [
|
|
125
|
+
[path.join(pluginDir, "skills"), WORKIT_METHOD_SKILLS],
|
|
126
|
+
];
|
|
127
|
+
for (const [dir, expected] of skillRoots) {
|
|
128
|
+
const mismatch = validateSkillManifests(dir, expected, "skills");
|
|
129
|
+
if (mismatch) errors.push(`plugin ${entry.name}: ${mismatch}`);
|
|
130
|
+
for (const skill of expected) {
|
|
131
|
+
const keys = frontmatterKeys(path.join(dir, skill, "SKILL.md"));
|
|
132
|
+
if (!keys.has("name"))
|
|
133
|
+
errors.push(`plugin ${entry.name}: ${skill}/SKILL.md missing frontmatter name`);
|
|
134
|
+
if (!keys.has("description"))
|
|
135
|
+
errors.push(`plugin ${entry.name}: ${skill}/SKILL.md missing frontmatter description`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const rulesDir = path.join(pluginDir, "rules");
|
|
139
|
+
if (existsSync(rulesDir)) {
|
|
140
|
+
for (const rule of readdirSync(rulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
141
|
+
const keys = frontmatterKeys(path.join(rulesDir, rule));
|
|
142
|
+
if (!keys.has("description"))
|
|
143
|
+
errors.push(`plugin ${entry.name}: ${rule} missing frontmatter description`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 6. No active runtime path targets an ignored dist file.
|
|
148
|
+
for (const rel of ["mcp.json", "hooks/hooks-cursor.json"]) {
|
|
149
|
+
const p = path.join(pluginDir, rel);
|
|
150
|
+
if (!existsSync(p)) continue;
|
|
151
|
+
const raw = readFileSync(p, "utf8");
|
|
152
|
+
if (raw.includes("dist/") || raw.includes("run-server") || raw.includes("session-start.js")) {
|
|
153
|
+
errors.push(`plugin ${entry.name}: ${rel} references ignored runtime output`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return errors;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (import.meta.main) {
|
|
162
|
+
const errors = validateMarketplace(root);
|
|
163
|
+
if (errors.length > 0) {
|
|
164
|
+
for (const e of errors) process.stderr.write(`${e}\n`);
|
|
165
|
+
process.stderr.write(
|
|
166
|
+
`marketplace validation FAILED (${errors.length} error${errors.length === 1 ? "" : "s"})\n`,
|
|
167
|
+
);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
console.log(`marketplace validation passed (root: ${root})`);
|
|
171
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Release guard: every adapter dist/ must inline the workspace's own
|
|
3
|
+
// @workit-dev sources, never a registry copy. bun build resolves the nearest
|
|
4
|
+
// node_modules first, so an installed packages/<pkg>/node_modules/@workit-dev
|
|
5
|
+
// copy silently replaces the tagged source (8.0.0 shipped 7.7.0's core that
|
|
6
|
+
// way). Two checks:
|
|
7
|
+
// - installs: no packages/*/node_modules/@workit-dev/* entry, and every root
|
|
8
|
+
// node_modules/@workit-dev/* entry resolves inside packages/ (run before
|
|
9
|
+
// the build; it also covers minified bundles, which carry no path headers);
|
|
10
|
+
// - bundles: bun prefixes each inlined module with a `// <path>` header; a
|
|
11
|
+
// header under node_modules/@workit-dev/ names a stale copy, and every
|
|
12
|
+
// non-minified entry (with its split chunks) must inline core from
|
|
13
|
+
// packages/workit-core/src/.
|
|
14
|
+
// `--installs-only` runs only the first check (prepareCmd, before the build).
|
|
15
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
16
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
17
|
+
import { RELEASE_PACKAGES } from "./analyze-release-scope";
|
|
18
|
+
|
|
19
|
+
// Separator-agnostic: a Windows build may write `\` in module headers.
|
|
20
|
+
const REGISTRY_COPY = /^\/\/ (.*node_modules[\\/]@workit-dev[\\/].+)$/gmu;
|
|
21
|
+
const WORKSPACE_CORE = /^\/\/ packages[\\/]workit-core[\\/]src[\\/]/mu;
|
|
22
|
+
/** bun's unminified output averages well under 100 bytes a line; --minify is thousands. */
|
|
23
|
+
const MINIFIED_BYTES_PER_LINE = 1000;
|
|
24
|
+
|
|
25
|
+
const entries = (dir: string): string[] => (existsSync(dir) ? readdirSync(dir) : []);
|
|
26
|
+
/** `root`-relative path with `/` separators, for messages on every OS. */
|
|
27
|
+
const rel = (root: string, file: string): string => relative(root, file).split(sep).join("/");
|
|
28
|
+
/** Windows paths compare case-insensitively. */
|
|
29
|
+
const fold = (file: string): string => (process.platform === "win32" ? file.toLowerCase() : file);
|
|
30
|
+
|
|
31
|
+
/** Installed @workit-dev packages a build could resolve instead of workspace source. */
|
|
32
|
+
export function registryInstalls(root: string): string[] {
|
|
33
|
+
const nested = entries(join(root, "packages")).flatMap((pkg) => {
|
|
34
|
+
const scope = join(root, "packages", pkg, "node_modules", "@workit-dev");
|
|
35
|
+
return entries(scope).map((name) => rel(root, join(scope, name)));
|
|
36
|
+
});
|
|
37
|
+
const packages = fold(join(realpathSync(root), "packages") + sep);
|
|
38
|
+
const scope = join(root, "node_modules", "@workit-dev");
|
|
39
|
+
const hoisted = entries(scope)
|
|
40
|
+
.filter((name) => !fold(realpathSync(join(scope, name))).startsWith(packages))
|
|
41
|
+
.map((name) => rel(root, join(scope, name)));
|
|
42
|
+
return [...nested, ...hoisted];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function bundles(dir: string): string[] {
|
|
46
|
+
return entries(dir).flatMap((name) => {
|
|
47
|
+
const file = join(dir, name);
|
|
48
|
+
if (statSync(file).isDirectory()) return bundles(file);
|
|
49
|
+
return name.endsWith(".js") ? [file] : [];
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type BundleProblem = { bundle: string; problem: string };
|
|
54
|
+
|
|
55
|
+
/** Sibling chunks a split bundle imports (`from "./x.js"`, `import("./x.js")`). */
|
|
56
|
+
const LOCAL_CHUNK = /(?:\bfrom|\bimport\s*\()\s*["']\.\/([^"']+\.js)["']/gu;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Built adapter bundles under `root`, and what each one inlined wrongly. A
|
|
60
|
+
* code-split entry (codex's hook) inlines core through its chunks, so the
|
|
61
|
+
* core-header requirement applies to each entry together with the chunks it
|
|
62
|
+
* reaches; a chunk only another bundle imports is not an entry.
|
|
63
|
+
*/
|
|
64
|
+
export function bundleSources(root: string): { scanned: string[]; problems: BundleProblem[] } {
|
|
65
|
+
const scanned = RELEASE_PACKAGES.filter((pkg) => pkg !== "workit-core").flatMap((pkg) =>
|
|
66
|
+
bundles(join(root, "packages", pkg, "dist")),
|
|
67
|
+
);
|
|
68
|
+
const text = new Map(scanned.map((file) => [file, readFileSync(file, "utf8")]));
|
|
69
|
+
const chunks = (file: string): string[] =>
|
|
70
|
+
[...(text.get(file) ?? "").matchAll(LOCAL_CHUNK)]
|
|
71
|
+
.map((m) => join(dirname(file), m[1]))
|
|
72
|
+
.filter((chunk) => text.has(chunk));
|
|
73
|
+
const imported = new Set(scanned.flatMap(chunks));
|
|
74
|
+
const reachesCore = (entry: string): boolean => {
|
|
75
|
+
const seen = new Set<string>();
|
|
76
|
+
const queue = [entry];
|
|
77
|
+
while (queue.length) {
|
|
78
|
+
const file = queue.shift() as string;
|
|
79
|
+
if (seen.has(file)) continue;
|
|
80
|
+
seen.add(file);
|
|
81
|
+
if (WORKSPACE_CORE.test(text.get(file) ?? "")) return true;
|
|
82
|
+
queue.push(...chunks(file));
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
};
|
|
86
|
+
const problems = scanned.flatMap((file): BundleProblem[] => {
|
|
87
|
+
const bundle = rel(root, file);
|
|
88
|
+
const body = text.get(file) ?? "";
|
|
89
|
+
const copies = [...body.matchAll(REGISTRY_COPY)].map((m) => m[1].replaceAll("\\", "/"));
|
|
90
|
+
if (copies.length)
|
|
91
|
+
return [{ bundle, problem: `${copies.length} registry-copy modules, e.g. ${copies[0]}` }];
|
|
92
|
+
const minified = body.length / (body.split("\n").length || 1) > MINIFIED_BYTES_PER_LINE;
|
|
93
|
+
if (minified || imported.has(file) || reachesCore(file)) return [];
|
|
94
|
+
const first = /^\s*\/\/ (\S.*)$/mu.exec(body)?.[1] ?? "none";
|
|
95
|
+
return [
|
|
96
|
+
{
|
|
97
|
+
bundle,
|
|
98
|
+
problem: `no \`// packages/workit-core/src/\` module header in it or its chunks (first header: ${first})`,
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
});
|
|
102
|
+
return { scanned: scanned.map((file) => rel(root, file)), problems };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Every failure under `root` as printable lines; empty when the release may proceed. */
|
|
106
|
+
export function verifyBundleSources(root: string, options: { installsOnly?: boolean } = {}) {
|
|
107
|
+
const failures = registryInstalls(root).map(
|
|
108
|
+
(path) => `${path}: installed copy outside packages/ (bundles would inline it)`,
|
|
109
|
+
);
|
|
110
|
+
if (options.installsOnly) return { failures, scanned: 0 };
|
|
111
|
+
const { scanned, problems } = bundleSources(root);
|
|
112
|
+
if (scanned.length === 0)
|
|
113
|
+
failures.push(`no adapter bundles under packages/*/dist; run \`bun run build\` first`);
|
|
114
|
+
for (const { bundle, problem } of problems) failures.push(`${bundle}: ${problem}`);
|
|
115
|
+
return { failures, scanned: scanned.length };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (import.meta.main) {
|
|
119
|
+
const args = process.argv.slice(2);
|
|
120
|
+
const installsOnly = args.includes("--installs-only");
|
|
121
|
+
const root = resolve(args.find((arg) => !arg.startsWith("--")) ?? process.cwd());
|
|
122
|
+
const { failures, scanned } = verifyBundleSources(root, { installsOnly });
|
|
123
|
+
if (failures.length) {
|
|
124
|
+
console.error("adapter bundles would not inline the workspace's own sources:");
|
|
125
|
+
for (const failure of failures) console.error(` - ${failure}`);
|
|
126
|
+
console.error("remove the copies and rebuild; see release.yml NPM_CONFIG_WORKSPACES_UPDATE");
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
console.log(
|
|
130
|
+
installsOnly
|
|
131
|
+
? "no registry copies of workspace packages installed"
|
|
132
|
+
: `verified ${scanned} adapter bundles inline workspace sources`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Rewrites packages/workit-core/schemas/ from the zod config schemas.
|
|
3
|
+
// test/workit-core/config-schemas.test.ts fails when a committed file drifts.
|
|
4
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { CONFIG_SCHEMA_FILES, renderConfigSchema } from "../src/core/config-schemas";
|
|
7
|
+
|
|
8
|
+
export const SCHEMAS_DIR = path.join(import.meta.dir, "..", "schemas");
|
|
9
|
+
|
|
10
|
+
if (import.meta.main) {
|
|
11
|
+
mkdirSync(SCHEMAS_DIR, { recursive: true });
|
|
12
|
+
for (const file of CONFIG_SCHEMA_FILES) {
|
|
13
|
+
writeFileSync(path.join(SCHEMAS_DIR, file), renderConfigSchema(file));
|
|
14
|
+
console.log(`wrote ${path.relative(process.cwd(), path.join(SCHEMAS_DIR, file))}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workit-architecture
|
|
3
|
+
description: Rank cited deepening refactors from churn and ledger friction; never applied unapproved. Use for architecture.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Architecture: deepen the modules that cause friction
|
|
8
|
+
|
|
9
|
+
User-invoked: offer it, never start it yourself. It proposes and grills; it
|
|
10
|
+
never refactors without the user's approval. Use the words in
|
|
11
|
+
`references/vocabulary.md` exactly and the domain words in `GLOSSARY.md`.
|
|
12
|
+
|
|
13
|
+
## Deepening
|
|
14
|
+
|
|
15
|
+
1. **Scope by YAGNI.** A direction the user names wins. Otherwise find hot
|
|
16
|
+
spots: churn (`git log --since=6.months --name-only --format=`, counted per
|
|
17
|
+
file, skipping lockfiles, version-bump manifests, generated files, docs and
|
|
18
|
+
`chore(release)` commits), friction in `workit ledger list --last 200`
|
|
19
|
+
(failed checks and verdicts, rulings, `retro:` decisions) and
|
|
20
|
+
`workit test-audit <paths> --json`. State the window and paths in one line.
|
|
21
|
+
2. **Settled decisions stand.** Read `GLOSSARY.md`, `CODING_STANDARDS.md` and
|
|
22
|
+
ADRs when present. Never re-litigate an ADR without new evidence.
|
|
23
|
+
3. **Explore organically.** A read-only explorer (Claude Code: Explore; or
|
|
24
|
+
you) walks the hot spots under the limits in `references/vocabulary.md`
|
|
25
|
+
and returns cited friction. Run the deletion test on every suspect.
|
|
26
|
+
4. **Report 3-5 ranked candidates, then stop and wait for approval.** Each:
|
|
27
|
+
- files and modules;
|
|
28
|
+
- the friction, with **2 or more cited occurrences** (commit, ledger row,
|
|
29
|
+
PR, test-audit finding); a one-off is not a candidate;
|
|
30
|
+
- an interface sketch: what callers see, what moves behind the seam;
|
|
31
|
+
- what gets simpler and what becomes testable through that interface;
|
|
32
|
+
- **merge danger**: one-way or two-way door, plus blast radius (callers,
|
|
33
|
+
packages, public surface).
|
|
34
|
+
The text list is primary; offer an HTML report only if the host renders one.
|
|
35
|
+
5. **Grill the chosen one**: numbered questions, each with a recommended
|
|
36
|
+
answer; challenge a weak premise with evidence first. Settle the interface,
|
|
37
|
+
the seam, the tests to replace and the migration order; a module named for
|
|
38
|
+
a new concept gets its glossary entry (workit-shape).
|
|
39
|
+
6. **Hand off on approval.** Several slices go into a plan for
|
|
40
|
+
`workit fanout plan <plan.json>`, with scopes and dependencies
|
|
41
|
+
(workit-fanout). One slice goes to a build (workit-implement). Record the
|
|
42
|
+
choice with `workit ledger decision "architecture: <id>" --why "<why>"`;
|
|
43
|
+
offer an ADR for a lasting rejection so the next run does not repeat it.
|
|
44
|
+
|
|
45
|
+
## Instruction files
|
|
46
|
+
|
|
47
|
+
Restructure AGENTS.md or CLAUDE.md in three escalating passes, **each its own
|
|
48
|
+
commit in one PR**, so the user can drop the later ones: remove no-ops;
|
|
49
|
+
progressive disclosure into topic files; coding rules to `CODING_STANDARDS.md`
|
|
50
|
+
and mechanical rules to checks. No commit adds a `workit knowledge lint`
|
|
51
|
+
finding. Never scaffold an empty file (`references/instruction-files.md`).
|
|
52
|
+
|
|
53
|
+
## Test sweep
|
|
54
|
+
|
|
55
|
+
Run `workit test-audit <paths> --json` over the suite or one hot spot and
|
|
56
|
+
report findings by module, worst first. On approval, the bad tests are
|
|
57
|
+
replaced (workit-test-audit).
|
|
58
|
+
|
|
59
|
+
## Example
|
|
60
|
+
|
|
61
|
+
Bad: "The CLI is messy, so I split router.ts in five." Uncited, unapproved.
|
|
62
|
+
|
|
63
|
+
Good: "1. Check runner (check.ts, evidence.ts), 3 occurrences: commits a1b2c3
|
|
64
|
+
and d4e5f6 edit both for one fix; ledger row 41 is a `check` red on a hidden
|
|
65
|
+
timeout. Sketch: `runCheck(spec)` returns evidence, the timeout behind it, so
|
|
66
|
+
timeouts test without a shell. Two-way door, 2 callers in one package."
|
|
67
|
+
|
|
68
|
+
## Check
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
workit knowledge lint # no new finding after each instruction-files commit
|
|
72
|
+
```
|