@amsterdamdatalabs/enact-extensions 0.1.1 → 0.1.5
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/README.md +4 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +82 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +187 -35
- package/dist/install.js.map +1 -1
- package/dist/internal/codex.d.ts.map +1 -1
- package/dist/internal/codex.js +7 -1
- package/dist/internal/codex.js.map +1 -1
- package/dist/internal/platform.d.ts +8 -0
- package/dist/internal/platform.d.ts.map +1 -1
- package/dist/internal/platform.js +46 -2
- package/dist/internal/platform.js.map +1 -1
- package/dist/provision.d.ts +30 -0
- package/dist/provision.d.ts.map +1 -0
- package/dist/provision.js +202 -0
- package/dist/provision.js.map +1 -0
- package/dist/validate/index.d.ts +23 -0
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js +80 -0
- package/dist/validate/index.js.map +1 -1
- package/extensions/enact-context/.agents/plugin.json +40 -0
- package/extensions/enact-context/.mcp.json +8 -0
- package/extensions/enact-context/README.md +25 -0
- package/extensions/enact-context/assets/icon.png +0 -0
- package/extensions/enact-context/assets/logo.png +0 -0
- package/extensions/enact-context/hooks/hooks.json +105 -0
- package/extensions/enact-context/skills/enact-context/SKILL.md +149 -0
- package/extensions/enact-context/skills/enact-context/scripts/install.sh +69 -0
- package/extensions/enact-factory/.agents/plugin.json +42 -0
- package/extensions/enact-factory/.mcp.json +8 -0
- package/extensions/enact-factory/assets/icon.png +0 -0
- package/extensions/enact-factory/assets/logo.png +0 -0
- package/extensions/enact-factory/hooks/user-prompt-submit.mjs +67 -0
- package/extensions/enact-factory/skills/testing-strategy/SKILL.md +167 -0
- package/extensions/enact-factory/skills/workitem-triage/SKILL.md +22 -0
- package/extensions/enact-operator/.agents/plugin.json +57 -0
- package/extensions/enact-operator/.app.json +3 -0
- package/extensions/enact-operator/.mcp.json +10 -0
- package/extensions/enact-operator/_taxonomy.md +86 -0
- package/extensions/enact-operator/agents/README.md +5 -0
- package/extensions/enact-operator/agents/architect.toml +25 -0
- package/extensions/enact-operator/agents/code-reviewer.toml +24 -0
- package/extensions/enact-operator/agents/critic.toml +30 -0
- package/extensions/enact-operator/agents/executor.toml +24 -0
- package/extensions/enact-operator/agents/explore.toml +23 -0
- package/extensions/enact-operator/agents/planner.toml +24 -0
- package/extensions/enact-operator/agents/verifier.toml +24 -0
- package/extensions/enact-operator/assets/icon.png +0 -0
- package/extensions/enact-operator/assets/logo.png +0 -0
- package/extensions/enact-operator/commands/doctor.md +39 -0
- package/extensions/enact-operator/commands/setup.md +51 -0
- package/extensions/enact-operator/hooks/hooks.json +126 -0
- package/extensions/enact-operator/skills/_variants.md +44 -0
- package/extensions/enact-operator/skills/ai-slop-cleaner/SKILL.md +50 -0
- package/extensions/enact-operator/skills/analyze/SKILL.md +91 -0
- package/extensions/enact-operator/skills/ask/SKILL.md +47 -0
- package/extensions/enact-operator/skills/autopilot/SKILL.md +170 -0
- package/extensions/enact-operator/skills/autoresearch-goal/SKILL.md +79 -0
- package/extensions/enact-operator/skills/cancel/SKILL.md +99 -0
- package/extensions/enact-operator/skills/configure-notifications/SKILL.md +77 -0
- package/extensions/enact-operator/skills/deep-interview/SKILL.md +80 -0
- package/extensions/enact-operator/skills/doctor/SKILL.md +48 -0
- package/extensions/enact-operator/skills/hud/SKILL.md +49 -0
- package/extensions/enact-operator/skills/hyperplan/SKILL.md +47 -0
- package/extensions/enact-operator/skills/plan/SKILL.md +78 -0
- package/extensions/enact-operator/skills/ralph/SKILL.md +201 -0
- package/extensions/enact-operator/skills/ralph/gemini.md +18 -0
- package/extensions/enact-operator/skills/ralplan/SKILL.md +151 -0
- package/extensions/enact-operator/skills/remove-deadcode/SKILL.md +45 -0
- package/extensions/enact-operator/skills/research/SKILL.md +74 -0
- package/extensions/enact-operator/skills/review/SKILL.md +58 -0
- package/extensions/enact-operator/skills/security-research/SKILL.md +54 -0
- package/extensions/enact-operator/skills/setup/SKILL.md +91 -0
- package/extensions/enact-operator/skills/setup/scripts/install.sh +50 -0
- package/extensions/enact-operator/skills/skill/SKILL.md +82 -0
- package/extensions/enact-operator/skills/tdd/SKILL.md +59 -0
- package/extensions/enact-operator/skills/team/SKILL.md +199 -0
- package/extensions/enact-operator/skills/trace/SKILL.md +41 -0
- package/extensions/enact-operator/skills/ultragoal/SKILL.md +99 -0
- package/extensions/enact-operator/skills/ultraqa/SKILL.md +113 -0
- package/extensions/enact-operator/skills/ultrawork/SKILL.md +145 -0
- package/extensions/enact-operator/skills/ultrawork/planner.md +28 -0
- package/extensions/enact-operator/skills/wiki/SKILL.md +41 -0
- package/extensions/enact-operator/skills/work-with-workitem/SKILL.md +51 -0
- package/extensions/enact-wiki/.agents/plugin.json +42 -0
- package/extensions/enact-wiki/.mcp.json +15 -0
- package/extensions/enact-wiki/README.md +44 -0
- package/extensions/enact-wiki/assets/icon.png +0 -0
- package/extensions/enact-wiki/assets/logo.png +0 -0
- package/extensions/enact-wiki/skills/document-parser/SKILL.md +17 -0
- package/extensions/enact-wiki/skills/document-parser/scripts/parse.sh +60 -0
- package/extensions/enact-wiki/skills/document-parser/skill.json +9 -0
- package/extensions/enact-wiki/skills/enact-wiki/SKILL.md +30 -0
- package/extensions/enact-wiki/skills/enact-wiki/references/ingest.md +62 -0
- package/extensions/enact-wiki/skills/enact-wiki/references/manage.md +34 -0
- package/extensions/enact-wiki/skills/enact-wiki/references/query.md +59 -0
- package/extensions/enact-wiki/skills/search-lab/SKILL.md +57 -0
- package/extensions/enact-wiki/skills/search-lab/scripts/analyze.ts +23 -0
- package/package.json +1 -1
- package/scripts/enact-extensions.mjs +79 -12
- package/scripts/lib/hooks.mjs +352 -0
- package/scripts/lib/ledger.mjs +4 -3
- package/scripts/lib/provision-mcp.mjs +12 -365
- package/scripts/lib/run-install.mjs +87 -5
- package/scripts/lib/run-prune.mjs +73 -0
- package/scripts/lib/run-sync.mjs +9 -1
- package/scripts/lib/run-uninstall.mjs +26 -2
- package/scripts/lib/run-validate.mjs +10 -1
- package/scripts/lib/serve.mjs +19 -1
- package/scripts/version-bump.sh +463 -0
- package/spec/codex.json +1 -11
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
defaultSharedHome,
|
|
10
10
|
} from "../../dist/index.js";
|
|
11
11
|
import { appendEntry } from "./ledger.mjs";
|
|
12
|
+
import { removePluginHooks } from "./hooks.mjs";
|
|
12
13
|
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// resolveHomes — mirrors run-install.mjs; same scope/per-platform-home logic.
|
|
@@ -19,7 +20,7 @@ function resolveHomes(options) {
|
|
|
19
20
|
return {
|
|
20
21
|
scope,
|
|
21
22
|
codexHome: options.codexHome ?? (scope === "local" ? local(".codex") : undefined),
|
|
22
|
-
enactHome: options.enactHome ?? (scope === "local" ? local(".enact") : defaultEnactHome()),
|
|
23
|
+
enactHome: options.enactHome ?? (scope === "local" ? local(".enact/agent") : defaultEnactHome()),
|
|
23
24
|
claudeHome: options.claudeHome ?? (scope === "local" ? local(".claude") : undefined),
|
|
24
25
|
cursorHome: options.cursorHome ?? (scope === "local" ? local(".cursor") : undefined),
|
|
25
26
|
sharedHome: options.sharedHome ?? (scope === "local" ? process.cwd() : defaultSharedHome()),
|
|
@@ -29,7 +30,7 @@ function resolveHomes(options) {
|
|
|
29
30
|
// ---------------------------------------------------------------------------
|
|
30
31
|
// ALL_PLATFORMS / parsePlatforms — reuse same logic as run-install.mjs
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
|
-
const ALL_PLATFORMS = ["codex", "claude", "cursor", "enact"
|
|
33
|
+
const ALL_PLATFORMS = ["codex", "claude", "cursor", "enact"];
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Expand "all" and split comma-separated platform lists.
|
|
@@ -83,11 +84,26 @@ function recordUninstall(name, version, platform, homes, options, result) {
|
|
|
83
84
|
scope: homes.scope,
|
|
84
85
|
home: homes[platformHomeField(platform)] ?? homes.enactHome,
|
|
85
86
|
path,
|
|
87
|
+
marketplaceName: options.marketplaceName ?? "enact-os-plugins",
|
|
86
88
|
},
|
|
87
89
|
options,
|
|
88
90
|
);
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
function removeInstalledPluginHooks(name, platform, homes, options) {
|
|
94
|
+
if (!["codex", "claude", "cursor", "enact"].includes(platform)) return;
|
|
95
|
+
const hookResult = removePluginHooks(platform, name, {
|
|
96
|
+
cwd: options.cwd ?? process.cwd(),
|
|
97
|
+
codexHome: platform === "codex" ? homes.codexHome : undefined,
|
|
98
|
+
claudeHome: platform === "claude" ? homes.claudeHome : undefined,
|
|
99
|
+
cursorHome: platform === "cursor" ? homes.cursorHome : undefined,
|
|
100
|
+
enactHome: platform === "enact" ? homes.enactHome : undefined,
|
|
101
|
+
});
|
|
102
|
+
if (hookResult.result !== "not_found" && hookResult.result !== "skipped") {
|
|
103
|
+
console.log(`hooks ${hookResult.result} for ${name} on ${platform} -> ${hookResult.location}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
91
107
|
function platformHomeField(platform) {
|
|
92
108
|
switch (platform) {
|
|
93
109
|
case "enact": return "enactHome";
|
|
@@ -109,6 +125,11 @@ function platformHomeField(platform) {
|
|
|
109
125
|
function runSinglePlatform(name, platform, homes, options) {
|
|
110
126
|
let result;
|
|
111
127
|
|
|
128
|
+
// Remove plugin hook registrations before platform uninstall rewrites host
|
|
129
|
+
// config files. Some serializers preserve hook tables but drop marker
|
|
130
|
+
// comments, so post-uninstall cleanup would lose its reversible marker.
|
|
131
|
+
removeInstalledPluginHooks(name, platform, homes, options);
|
|
132
|
+
|
|
112
133
|
switch (platform) {
|
|
113
134
|
case "enact":
|
|
114
135
|
result = uninstallCodexPluginBundle(name, {
|
|
@@ -143,6 +164,7 @@ function runSinglePlatform(name, platform, homes, options) {
|
|
|
143
164
|
}
|
|
144
165
|
|
|
145
166
|
if (result.noop) {
|
|
167
|
+
removeInstalledPluginHooks(name, platform, homes, options);
|
|
146
168
|
process.stderr.write(
|
|
147
169
|
`[enact-extensions uninstall] Nothing to remove for ${name} on ${platform} (not installed).\n`,
|
|
148
170
|
);
|
|
@@ -154,6 +176,8 @@ function runSinglePlatform(name, platform, homes, options) {
|
|
|
154
176
|
}
|
|
155
177
|
console.log(`uninstalled ${name} from ${platform}`);
|
|
156
178
|
|
|
179
|
+
removeInstalledPluginHooks(name, platform, homes, options);
|
|
180
|
+
|
|
157
181
|
// Record uninstall in ledger (best-effort)
|
|
158
182
|
recordUninstall(name, options.version, platform, homes, options, result);
|
|
159
183
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
validatePluginBundleFromCanonical,
|
|
3
3
|
checkPluginBundleComponentPathsFromCanonical,
|
|
4
|
+
checkHookEvents,
|
|
4
5
|
} from "../../dist/index.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
export function runValidate(pluginRoot, platforms) {
|
|
14
15
|
const report = validatePluginBundleFromCanonical(pluginRoot, platforms);
|
|
15
16
|
const warnings = checkPluginBundleComponentPathsFromCanonical(pluginRoot);
|
|
17
|
+
const hookErrors = checkHookEvents(pluginRoot);
|
|
16
18
|
|
|
17
19
|
for (const result of report.results) {
|
|
18
20
|
const status = result.ok ? "ok" : "FAIL";
|
|
@@ -22,9 +24,16 @@ export function runValidate(pluginRoot, platforms) {
|
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
if (hookErrors.length > 0) {
|
|
28
|
+
console.log(`[FAIL] hooks (${pluginRoot})`);
|
|
29
|
+
for (const err of hookErrors) {
|
|
30
|
+
console.log(` - ${err}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
for (const warn of warnings) {
|
|
26
35
|
console.log(`[warn] ${warn}`);
|
|
27
36
|
}
|
|
28
37
|
|
|
29
|
-
return report.ok && warnings.length === 0;
|
|
38
|
+
return report.ok && warnings.length === 0 && hookErrors.length === 0;
|
|
30
39
|
}
|
package/scripts/lib/serve.mjs
CHANGED
|
@@ -436,9 +436,27 @@ export function createServer(opts = {}) {
|
|
|
436
436
|
* @param {object} [opts.installDefaults] - Merged into runInstall/runUninstall calls.
|
|
437
437
|
* @returns {Promise<{ server: http.Server, url: string }>}
|
|
438
438
|
*/
|
|
439
|
+
// ENACT OS port-registry convention: dev listeners live in 43xxx, prod in 53xxx.
|
|
440
|
+
export const DEV_PORT = 43217;
|
|
441
|
+
export const PROD_PORT = 53217;
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Resolve the serve port: an explicit `--port` always wins; otherwise `--prod`
|
|
445
|
+
* selects the prod-series port (53217) and the default is the dev-series port
|
|
446
|
+
* (43217). Pure (no I/O) so it is unit-testable without binding a socket.
|
|
447
|
+
* @param {object} [opts]
|
|
448
|
+
* @param {number} [opts.port] - Explicit port (wins; 0 = ephemeral).
|
|
449
|
+
* @param {boolean} [opts.prod] - Use the prod-series port (53217).
|
|
450
|
+
* @returns {number}
|
|
451
|
+
*/
|
|
452
|
+
export function resolveServePort(opts = {}) {
|
|
453
|
+
if (opts.port !== undefined && opts.port !== null) return opts.port;
|
|
454
|
+
return opts.prod ? PROD_PORT : DEV_PORT;
|
|
455
|
+
}
|
|
456
|
+
|
|
439
457
|
export function startServer(opts = {}) {
|
|
440
458
|
const host = opts.host ?? "127.0.0.1";
|
|
441
|
-
const port = opts
|
|
459
|
+
const port = resolveServePort(opts);
|
|
442
460
|
|
|
443
461
|
const server = createServer(opts);
|
|
444
462
|
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
# version-bump.sh
|
|
4
|
+
# Auto-bumps patch version (x.y.z → x.y.z+1) on a feat/* or fix/* branch,
|
|
5
|
+
# as the LAST CI step before the PR merges into integration.
|
|
6
|
+
#
|
|
7
|
+
# Strategy (runs on the feature branch, not on integration):
|
|
8
|
+
# 1. Must be on a feat/* or fix/* branch — not on integration itself.
|
|
9
|
+
# 2. Fetches origin/integration and compares versions:
|
|
10
|
+
# Same → PR did not manually bump → auto-bump on the feature branch.
|
|
11
|
+
# Different → developer bumped intentionally in the PR → skip silently.
|
|
12
|
+
# 3. Commits + pushes the bump back to the SAME feature branch (***NO_CI***
|
|
13
|
+
# prevents a second pipeline run). The PR then merges to integration
|
|
14
|
+
# already carrying the bumped version.
|
|
15
|
+
#
|
|
16
|
+
# Why not push to integration directly?
|
|
17
|
+
# Azure DevOps branch policy (TF402455) blocks direct pushes to protected
|
|
18
|
+
# branches. We never bypass this — instead we bump on the feature branch.
|
|
19
|
+
#
|
|
20
|
+
# Supported manifest types — detected from filename, no scanning:
|
|
21
|
+
# package.json → npm (.version)
|
|
22
|
+
# pyproject.toml → python ([project] version, uv / PEP 621)
|
|
23
|
+
# Cargo.toml → rust ([package] version)
|
|
24
|
+
#
|
|
25
|
+
# Usage:
|
|
26
|
+
# ./scripts/version-bump.sh --file <path/to/manifest> [OPTIONS]
|
|
27
|
+
#
|
|
28
|
+
# Options:
|
|
29
|
+
# -f, --file <path> REQUIRED. Exact path to the version manifest.
|
|
30
|
+
# -b, --branch <name> Integration branch to compare against (default: integration)
|
|
31
|
+
# --push Push the bump commit back to the feature branch
|
|
32
|
+
# -n, --dry-run Print what would change; no edits, no commit
|
|
33
|
+
# -h, --help Show this help
|
|
34
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
set -euo pipefail
|
|
36
|
+
|
|
37
|
+
# ── ANSI colours ──────────────────────────────────────────────────────────────
|
|
38
|
+
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
|
39
|
+
BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m'
|
|
40
|
+
info() { printf "${BLUE}[INFO]${NC} %s\n" "$*"; }
|
|
41
|
+
ok() { printf "${GREEN}[OK]${NC} %s\n" "$*"; }
|
|
42
|
+
warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*"; }
|
|
43
|
+
die() { printf "${RED}[ERROR]${NC} %s\n" "$*" >&2; exit 1; }
|
|
44
|
+
step() { printf "\n${BOLD}▶ %s${NC}\n" "$*"; }
|
|
45
|
+
|
|
46
|
+
# ── State ─────────────────────────────────────────────────────────────────────
|
|
47
|
+
VERSION_FILE=""
|
|
48
|
+
PROJECT_TYPE=""
|
|
49
|
+
GIT_REPO_ROOT=""
|
|
50
|
+
CURRENT_VERSION=""
|
|
51
|
+
CURRENT_BRANCH=""
|
|
52
|
+
INTEGRATION_BRANCH="integration"
|
|
53
|
+
DRY_RUN=false
|
|
54
|
+
DO_PUSH=false
|
|
55
|
+
|
|
56
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
usage() {
|
|
58
|
+
cat <<'HELP'
|
|
59
|
+
version-bump.sh — Bump patch version (x.y.z → x.y.z+1) on a feat/fix branch before merge
|
|
60
|
+
|
|
61
|
+
USAGE
|
|
62
|
+
./scripts/version-bump.sh --file <path> [OPTIONS]
|
|
63
|
+
|
|
64
|
+
OPTIONS
|
|
65
|
+
-f, --file <path> REQUIRED. Exact path to the version manifest:
|
|
66
|
+
package.json → npm
|
|
67
|
+
pyproject.toml → python (PEP 621 / uv)
|
|
68
|
+
Cargo.toml → rust
|
|
69
|
+
-b, --branch <name> Integration branch to compare against (default: integration)
|
|
70
|
+
--push Push the bump commit back to the feature branch after committing
|
|
71
|
+
-n, --dry-run Show what would change; do not modify or commit
|
|
72
|
+
-h, --help Show this message
|
|
73
|
+
|
|
74
|
+
GUARDS
|
|
75
|
+
1. Must be on a feat/* or fix/* branch — never runs on integration directly.
|
|
76
|
+
2. Fetches origin/<integration-branch> and compares versions:
|
|
77
|
+
Same → PR did not manually bump the version → auto-bump.
|
|
78
|
+
Different → developer bumped intentionally in the PR → skip silently.
|
|
79
|
+
|
|
80
|
+
PUSH TARGET
|
|
81
|
+
The bump commit is pushed back to the CURRENT feature branch (not integration).
|
|
82
|
+
Azure DevOps branch policies block direct pushes to integration; we never bypass them.
|
|
83
|
+
|
|
84
|
+
CI SKIP
|
|
85
|
+
Bump commit message contains ***NO_CI*** — Azure DevOps' official keyword
|
|
86
|
+
to prevent the resulting push from re-triggering the pipeline.
|
|
87
|
+
|
|
88
|
+
AZURE PIPELINES — add as last step in the CI job, covering PR builds on feat/* / fix/*:
|
|
89
|
+
|
|
90
|
+
- script: |
|
|
91
|
+
git config user.email "pipeline@build.com"
|
|
92
|
+
git config user.name "Azure Pipelines"
|
|
93
|
+
git remote set-url origin \
|
|
94
|
+
"https://x-token:$(System.AccessToken)@dev.azure.com/org/project/_git/repo"
|
|
95
|
+
bash scripts/version-bump.sh --file package.json --push
|
|
96
|
+
displayName: Bump patch version
|
|
97
|
+
condition: |
|
|
98
|
+
and(
|
|
99
|
+
succeeded(),
|
|
100
|
+
or(
|
|
101
|
+
startsWith(variables['System.PullRequest.SourceBranch'], 'refs/heads/feat/'),
|
|
102
|
+
startsWith(variables['System.PullRequest.SourceBranch'], 'refs/heads/fix/'),
|
|
103
|
+
startsWith(variables['Build.SourceBranch'], 'refs/heads/feat/'),
|
|
104
|
+
startsWith(variables['Build.SourceBranch'], 'refs/heads/fix/')
|
|
105
|
+
),
|
|
106
|
+
not(contains(variables['Build.SourceVersionMessage'], '***NO_CI***'))
|
|
107
|
+
)
|
|
108
|
+
env:
|
|
109
|
+
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
|
110
|
+
BUILD_SOURCEBRANCH: $(Build.SourceBranch)
|
|
111
|
+
SYSTEM_PULLREQUEST_SOURCEBRANCH: $(System.PullRequest.SourceBranch)
|
|
112
|
+
HELP
|
|
113
|
+
exit 0
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
117
|
+
parse_args() {
|
|
118
|
+
local file_given=false
|
|
119
|
+
while [[ $# -gt 0 ]]; do
|
|
120
|
+
case "$1" in
|
|
121
|
+
-f|--file) VERSION_FILE="${2:?'--file requires a path'}"; file_given=true; shift 2 ;;
|
|
122
|
+
-b|--branch) INTEGRATION_BRANCH="${2:?'--branch requires a name'}"; shift 2 ;;
|
|
123
|
+
--push) DO_PUSH=true; shift ;;
|
|
124
|
+
-n|--dry-run) DRY_RUN=true; shift ;;
|
|
125
|
+
-h|--help) usage ;;
|
|
126
|
+
*) die "Unknown option: '$1'. Run with --help." ;;
|
|
127
|
+
esac
|
|
128
|
+
done
|
|
129
|
+
$file_given || die "--file <path> is required. Example: --file package.json"
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
133
|
+
# Resolve manifest + detect type from filename (no scanning)
|
|
134
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
135
|
+
resolve_manifest() {
|
|
136
|
+
step "Resolving manifest"
|
|
137
|
+
[[ -f "$VERSION_FILE" ]] || die "Manifest not found: '${VERSION_FILE}'"
|
|
138
|
+
|
|
139
|
+
# Physical absolute path (handles /tmp → /private/tmp on macOS etc.)
|
|
140
|
+
VERSION_FILE="$(cd "$(dirname "$VERSION_FILE")" && pwd -P)/$(basename "$VERSION_FILE")"
|
|
141
|
+
|
|
142
|
+
local base
|
|
143
|
+
base="$(basename "$VERSION_FILE")"
|
|
144
|
+
case "$base" in
|
|
145
|
+
package.json) PROJECT_TYPE="npm" ;;
|
|
146
|
+
pyproject.toml) PROJECT_TYPE="python" ;;
|
|
147
|
+
Cargo.toml) PROJECT_TYPE="rust" ;;
|
|
148
|
+
*) die "Unrecognised manifest '${base}'. Expected: package.json | pyproject.toml | Cargo.toml" ;;
|
|
149
|
+
esac
|
|
150
|
+
|
|
151
|
+
local file_dir
|
|
152
|
+
file_dir="$(dirname "$VERSION_FILE")"
|
|
153
|
+
GIT_REPO_ROOT="$(git -C "$file_dir" rev-parse --show-toplevel 2>/dev/null)" || \
|
|
154
|
+
die "'${VERSION_FILE}' is not inside a git repository."
|
|
155
|
+
GIT_REPO_ROOT="$(cd "$GIT_REPO_ROOT" && pwd -P)"
|
|
156
|
+
|
|
157
|
+
info "Manifest : ${VERSION_FILE}"
|
|
158
|
+
info "Type : ${BOLD}${PROJECT_TYPE}${NC}"
|
|
159
|
+
info "Git repo root : ${GIT_REPO_ROOT}"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
163
|
+
# Version readers
|
|
164
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
165
|
+
_read_npm() {
|
|
166
|
+
node -e "const fs=require('fs'); process.stdout.write(JSON.parse(fs.readFileSync('$1','utf8')).version)"
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_read_python() {
|
|
170
|
+
python3 - "$1" <<'PY'
|
|
171
|
+
import sys, re
|
|
172
|
+
text = open(sys.argv[1]).read()
|
|
173
|
+
def find_in_section(pat, text):
|
|
174
|
+
in_sect = False
|
|
175
|
+
for line in text.splitlines():
|
|
176
|
+
s = line.strip()
|
|
177
|
+
if re.match(pat, s): in_sect = True; continue
|
|
178
|
+
if re.match(r'^\[', s): in_sect = False
|
|
179
|
+
if in_sect:
|
|
180
|
+
m = re.match(r'^version\s*=\s*["\']([^"\']+)["\']', s)
|
|
181
|
+
if m: return m.group(1)
|
|
182
|
+
return None
|
|
183
|
+
ver = (find_in_section(r'^\[project\]', text)
|
|
184
|
+
or find_in_section(r'^\[tool\.poetry\]', text))
|
|
185
|
+
if not ver:
|
|
186
|
+
m = re.search(r'^version\s*=\s*["\']([^"\']+)["\']', text, re.M)
|
|
187
|
+
ver = m.group(1) if m else None
|
|
188
|
+
if ver: sys.stdout.write(ver); sys.exit(0)
|
|
189
|
+
sys.stderr.write(f"version not found in {sys.argv[1]}\n"); sys.exit(1)
|
|
190
|
+
PY
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
_read_rust() {
|
|
194
|
+
python3 - "$1" <<'PY'
|
|
195
|
+
import sys, re
|
|
196
|
+
in_pkg = False
|
|
197
|
+
for line in open(sys.argv[1]):
|
|
198
|
+
s = line.strip()
|
|
199
|
+
if re.match(r'^\[package\]', s): in_pkg = True; continue
|
|
200
|
+
if re.match(r'^\[', s): in_pkg = False
|
|
201
|
+
if in_pkg:
|
|
202
|
+
m = re.match(r'^version\s*=\s*["\']([^"\']+)["\']', s)
|
|
203
|
+
if m: sys.stdout.write(m.group(1)); sys.exit(0)
|
|
204
|
+
sys.stderr.write(f"[package] version not found in {sys.argv[1]}\n"); sys.exit(1)
|
|
205
|
+
PY
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
get_version() {
|
|
209
|
+
local file="${1:-$VERSION_FILE}"
|
|
210
|
+
case "$PROJECT_TYPE" in
|
|
211
|
+
npm) _read_npm "$file" ;;
|
|
212
|
+
python) _read_python "$file" ;;
|
|
213
|
+
rust) _read_rust "$file" ;;
|
|
214
|
+
esac
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
get_version_at_ref() {
|
|
218
|
+
local git_ref="$1"
|
|
219
|
+
local rel_file="${VERSION_FILE#"${GIT_REPO_ROOT}/"}"
|
|
220
|
+
local tmp
|
|
221
|
+
tmp="$(mktemp)"
|
|
222
|
+
trap "rm -f '${tmp}'" RETURN
|
|
223
|
+
git -C "$GIT_REPO_ROOT" show "${git_ref}:${rel_file}" > "$tmp" 2>/dev/null || \
|
|
224
|
+
die "Cannot read '${rel_file}' at ref '${git_ref}'. Is the file committed?"
|
|
225
|
+
get_version "$tmp"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
229
|
+
# Patch bumper x.y.z → x.y.(z+1)
|
|
230
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
231
|
+
bump_patch() {
|
|
232
|
+
local core="${1%%[-+]*}"
|
|
233
|
+
local major minor patch
|
|
234
|
+
IFS='.' read -r major minor patch <<< "$core"
|
|
235
|
+
[[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ && "$patch" =~ ^[0-9]+$ ]] || \
|
|
236
|
+
die "Cannot parse '${1}' as semver x.y.z"
|
|
237
|
+
echo "${major}.${minor}.$((patch + 1))"
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
241
|
+
# Version writers
|
|
242
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
243
|
+
_write_npm() {
|
|
244
|
+
python3 - "$1" "$2" <<'PY'
|
|
245
|
+
import json, sys
|
|
246
|
+
path, ver = sys.argv[1], sys.argv[2]
|
|
247
|
+
d = json.load(open(path))
|
|
248
|
+
d['version'] = ver
|
|
249
|
+
with open(path, 'w') as f:
|
|
250
|
+
json.dump(d, f, indent=2, ensure_ascii=False); f.write('\n')
|
|
251
|
+
PY
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
_write_python() {
|
|
255
|
+
python3 - "$1" "$2" <<'PY'
|
|
256
|
+
import sys, re
|
|
257
|
+
path, new_ver = sys.argv[1], sys.argv[2]
|
|
258
|
+
lines = open(path).readlines()
|
|
259
|
+
SECTIONS = [r'^\[project\]', r'^\[tool\.poetry\]']
|
|
260
|
+
in_target = replaced = False
|
|
261
|
+
result = []
|
|
262
|
+
for line in lines:
|
|
263
|
+
s = line.strip()
|
|
264
|
+
if any(re.match(p, s) for p in SECTIONS): in_target = True
|
|
265
|
+
elif re.match(r'^\[', s): in_target = False
|
|
266
|
+
if in_target and not replaced:
|
|
267
|
+
new_line = re.sub(r'^(version\s*=\s*)["\']([^"\']+)["\']',
|
|
268
|
+
lambda m: m.group(1) + '"' + new_ver + '"', line)
|
|
269
|
+
if new_line != line: replaced = True; line = new_line
|
|
270
|
+
result.append(line)
|
|
271
|
+
if not replaced:
|
|
272
|
+
full = re.sub(r'^(version\s*=\s*)["\']([^"\']+)["\']',
|
|
273
|
+
lambda m: m.group(1) + '"' + new_ver + '"',
|
|
274
|
+
''.join(result), count=1, flags=re.M)
|
|
275
|
+
open(path, 'w').write(full)
|
|
276
|
+
else:
|
|
277
|
+
open(path, 'w').writelines(result)
|
|
278
|
+
PY
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
_write_rust() {
|
|
282
|
+
python3 - "$1" "$2" <<'PY'
|
|
283
|
+
import sys, re
|
|
284
|
+
path, new_ver = sys.argv[1], sys.argv[2]
|
|
285
|
+
lines = open(path).readlines()
|
|
286
|
+
in_pkg = replaced = False
|
|
287
|
+
result = []
|
|
288
|
+
for line in lines:
|
|
289
|
+
s = line.strip()
|
|
290
|
+
if re.match(r'^\[package\]', s): in_pkg = True
|
|
291
|
+
elif re.match(r'^\[', s): in_pkg = False
|
|
292
|
+
if in_pkg and not replaced:
|
|
293
|
+
new_line = re.sub(r'^(version\s*=\s*)["\']([^"\']+)["\']',
|
|
294
|
+
lambda m: m.group(1) + '"' + new_ver + '"', line)
|
|
295
|
+
if new_line != line: replaced = True; line = new_line
|
|
296
|
+
result.append(line)
|
|
297
|
+
open(path, 'w').writelines(result)
|
|
298
|
+
PY
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
set_version() {
|
|
302
|
+
case "$PROJECT_TYPE" in
|
|
303
|
+
npm) _write_npm "$VERSION_FILE" "$1" ;;
|
|
304
|
+
python) _write_python "$VERSION_FILE" "$1" ;;
|
|
305
|
+
rust) _write_rust "$VERSION_FILE" "$1" ;;
|
|
306
|
+
esac
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
310
|
+
# Lock file updates (best-effort, non-fatal)
|
|
311
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
312
|
+
update_lock_files() {
|
|
313
|
+
case "$PROJECT_TYPE" in
|
|
314
|
+
rust)
|
|
315
|
+
if command -v cargo &>/dev/null; then
|
|
316
|
+
info "Regenerating Cargo.lock (cargo update --workspace)…"
|
|
317
|
+
cargo update --workspace --quiet 2>/dev/null || warn "cargo update failed — Cargo.lock may be stale."
|
|
318
|
+
else
|
|
319
|
+
warn "cargo not found — Cargo.lock NOT updated."
|
|
320
|
+
fi ;;
|
|
321
|
+
python)
|
|
322
|
+
if command -v uv &>/dev/null; then
|
|
323
|
+
info "Regenerating uv.lock (uv lock)…"
|
|
324
|
+
uv lock --quiet 2>/dev/null || warn "uv lock failed — uv.lock may be stale."
|
|
325
|
+
fi ;;
|
|
326
|
+
npm)
|
|
327
|
+
warn "npm lock file NOT touched — run 'npm install' locally to regenerate." ;;
|
|
328
|
+
esac
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
332
|
+
# Guards
|
|
333
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
334
|
+
validate_branch() {
|
|
335
|
+
step "Validating source branch"
|
|
336
|
+
local branch
|
|
337
|
+
branch="$(git -C "$GIT_REPO_ROOT" rev-parse --abbrev-ref HEAD)"
|
|
338
|
+
|
|
339
|
+
# Azure Pipelines checks out in detached HEAD state.
|
|
340
|
+
# In PR builds, SYSTEM_PULLREQUEST_SOURCEBRANCH gives the real source branch.
|
|
341
|
+
# In IndividualCI builds, BUILD_SOURCEBRANCH gives the branch.
|
|
342
|
+
if [[ "$branch" == "HEAD" ]]; then
|
|
343
|
+
local pr_branch="${SYSTEM_PULLREQUEST_SOURCEBRANCH:-}"
|
|
344
|
+
local ci_branch="${BUILD_SOURCEBRANCH:-${GITHUB_REF:-}}"
|
|
345
|
+
# Azure DevOps passes '$(Var.Name)' literally when the variable is undefined.
|
|
346
|
+
# Discard any value that looks like an unexpanded template.
|
|
347
|
+
[[ "$pr_branch" == '$('* ]] && pr_branch=""
|
|
348
|
+
[[ "$ci_branch" == '$('* ]] && ci_branch=""
|
|
349
|
+
# PR build: prefer the PR source branch (not the synthetic merge ref)
|
|
350
|
+
local env_branch="${pr_branch:-$ci_branch}"
|
|
351
|
+
branch="${env_branch#refs/heads/}"
|
|
352
|
+
[[ -n "$branch" ]] || \
|
|
353
|
+
die "Detached HEAD and no branch env var set. Cannot determine branch."
|
|
354
|
+
info "Detached HEAD — branch from env: ${branch}"
|
|
355
|
+
fi
|
|
356
|
+
|
|
357
|
+
[[ "$branch" =~ ^(feat|fix)/ ]] || \
|
|
358
|
+
die "Expected a feat/* or fix/* branch, currently on '${branch}'. Version bump runs on feature branches before PR merge."
|
|
359
|
+
|
|
360
|
+
ok "On feature branch '${branch}'."
|
|
361
|
+
CURRENT_BRANCH="$branch"
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
# Guard: compare current branch version against origin/integration.
|
|
365
|
+
# Same → PR did not manually bump → auto-bump this branch.
|
|
366
|
+
# Different → manual bump in PR → skip silently (exit 0).
|
|
367
|
+
validate_version_unchanged() {
|
|
368
|
+
step "Comparing version against origin/${INTEGRATION_BRANCH}"
|
|
369
|
+
|
|
370
|
+
local rel_file="${VERSION_FILE#"${GIT_REPO_ROOT}/"}"
|
|
371
|
+
local v_now v_integration
|
|
372
|
+
|
|
373
|
+
v_now="$(get_version)" || die "Cannot read current version from manifest."
|
|
374
|
+
|
|
375
|
+
# Fetch latest integration so our remote tracking ref is up to date.
|
|
376
|
+
git -C "$GIT_REPO_ROOT" fetch origin "${INTEGRATION_BRANCH}" --quiet 2>/dev/null || \
|
|
377
|
+
warn "Could not fetch origin/${INTEGRATION_BRANCH} — using cached remote tracking branch."
|
|
378
|
+
|
|
379
|
+
# If the manifest doesn't exist on integration yet (new project), just bump.
|
|
380
|
+
if ! git -C "$GIT_REPO_ROOT" show "origin/${INTEGRATION_BRANCH}:${rel_file}" >/dev/null 2>&1; then
|
|
381
|
+
warn "Manifest '${rel_file}' not found on '${INTEGRATION_BRANCH}' — treating as new project; bumping."
|
|
382
|
+
CURRENT_VERSION="$v_now"
|
|
383
|
+
return
|
|
384
|
+
fi
|
|
385
|
+
|
|
386
|
+
local tmp
|
|
387
|
+
tmp="$(mktemp)"
|
|
388
|
+
trap "rm -f '${tmp}'" RETURN
|
|
389
|
+
git -C "$GIT_REPO_ROOT" show "origin/${INTEGRATION_BRANCH}:${rel_file}" > "$tmp" 2>/dev/null
|
|
390
|
+
v_integration="$(get_version "$tmp")" || die "Cannot read version from origin/${INTEGRATION_BRANCH}."
|
|
391
|
+
|
|
392
|
+
info "Version on this branch : ${v_now}"
|
|
393
|
+
info "Version on ${INTEGRATION_BRANCH} : ${v_integration}"
|
|
394
|
+
|
|
395
|
+
if [[ "$v_now" != "$v_integration" ]]; then
|
|
396
|
+
printf "\n${YELLOW}${BOLD}↳ Version differs (%s here vs %s on %s). Manual bump detected — skipping auto-bump.${NC}\n\n" \
|
|
397
|
+
"$v_now" "$v_integration" "$INTEGRATION_BRANCH"
|
|
398
|
+
exit 0 # clean exit — not an error
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
ok "Versions match at ${BOLD}${v_now}${NC} — safe to auto-bump."
|
|
402
|
+
CURRENT_VERSION="$v_now"
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
406
|
+
# Commit — ***NO_CI*** suppresses Azure DevOps CI on the resulting push
|
|
407
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
408
|
+
commit_bump() {
|
|
409
|
+
local new_ver="$1"
|
|
410
|
+
git -C "$GIT_REPO_ROOT" add "$VERSION_FILE"
|
|
411
|
+
case "$PROJECT_TYPE" in
|
|
412
|
+
rust) [[ -f "${GIT_REPO_ROOT}/Cargo.lock" ]] && git -C "$GIT_REPO_ROOT" add "${GIT_REPO_ROOT}/Cargo.lock" || true ;;
|
|
413
|
+
python) [[ -f "${GIT_REPO_ROOT}/uv.lock" ]] && git -C "$GIT_REPO_ROOT" add "${GIT_REPO_ROOT}/uv.lock" || true ;;
|
|
414
|
+
esac
|
|
415
|
+
git -C "$GIT_REPO_ROOT" commit -m "chore: bump version ${CURRENT_VERSION} → ${new_ver} ***NO_CI***"
|
|
416
|
+
ok "Committed: chore: bump version ${CURRENT_VERSION} → ${new_ver} ***NO_CI***"
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
push_bump() {
|
|
420
|
+
local remote
|
|
421
|
+
remote="$(git -C "$GIT_REPO_ROOT" remote | head -1)"
|
|
422
|
+
[[ -n "$remote" ]] || die "No git remote found — cannot push."
|
|
423
|
+
info "Pushing to ${remote}/${CURRENT_BRANCH}…"
|
|
424
|
+
git -C "$GIT_REPO_ROOT" push "$remote" "HEAD:${CURRENT_BRANCH}"
|
|
425
|
+
ok "Pushed to ${remote}/${CURRENT_BRANCH}."
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
429
|
+
# Main
|
|
430
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
431
|
+
main() {
|
|
432
|
+
parse_args "$@"
|
|
433
|
+
resolve_manifest
|
|
434
|
+
validate_branch
|
|
435
|
+
validate_version_unchanged # exits 0 silently if version was manually bumped
|
|
436
|
+
|
|
437
|
+
local new_version
|
|
438
|
+
new_version="$(bump_patch "$CURRENT_VERSION")"
|
|
439
|
+
|
|
440
|
+
step "Version bump"
|
|
441
|
+
info " ${CURRENT_VERSION} → ${BOLD}${new_version}${NC}"
|
|
442
|
+
|
|
443
|
+
if $DRY_RUN; then
|
|
444
|
+
warn "Dry-run — no files modified, no commit made."
|
|
445
|
+
return 0
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
set_version "$new_version"
|
|
449
|
+
ok "Updated ${VERSION_FILE}"
|
|
450
|
+
update_lock_files "$new_version"
|
|
451
|
+
|
|
452
|
+
step "Committing"
|
|
453
|
+
commit_bump "$new_version"
|
|
454
|
+
|
|
455
|
+
if $DO_PUSH; then
|
|
456
|
+
step "Pushing"
|
|
457
|
+
push_bump
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
printf "\n${GREEN}${BOLD}✓ Done.${NC} Version bumped to ${BOLD}%s${NC}\n\n" "$new_version"
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
main "$@"
|
package/spec/codex.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "codex-plugin.json",
|
|
4
4
|
"title": "Codex Plugin Manifest",
|
|
5
|
-
"description": "Validates .codex-plugin/plugin.json for OpenAI Codex plugins.
|
|
5
|
+
"description": "Validates .codex-plugin/plugin.json for OpenAI Codex plugins. Based on the OpenAI Codex plugin build docs.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"name"
|
|
@@ -69,11 +69,6 @@
|
|
|
69
69
|
"description": "Relative path to the skills directory.",
|
|
70
70
|
"default": "./skills/"
|
|
71
71
|
},
|
|
72
|
-
"commands": {
|
|
73
|
-
"type": "string",
|
|
74
|
-
"description": "Relative path to slash commands directory (flat Markdown files).",
|
|
75
|
-
"default": "./commands/"
|
|
76
|
-
},
|
|
77
72
|
"mcpServers": {
|
|
78
73
|
"type": "string",
|
|
79
74
|
"description": "Relative path to the .mcp.json MCP server configuration file.",
|
|
@@ -144,11 +139,6 @@
|
|
|
144
139
|
"description": "Human-readable list of capabilities shown in the plugin directory."
|
|
145
140
|
}
|
|
146
141
|
}
|
|
147
|
-
},
|
|
148
|
-
"agents": {
|
|
149
|
-
"type": "string",
|
|
150
|
-
"description": "Relative path to the agents dir.",
|
|
151
|
-
"default": "./agents/"
|
|
152
142
|
}
|
|
153
143
|
}
|
|
154
144
|
}
|