agentsys 5.8.3 → 5.8.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.kiro/agents/worktree-manager.json +1 -1
- package/.kiro/skills/discover-tasks/SKILL.md +3 -3
- package/.kiro/skills/web-auth/SKILL.md +16 -16
- package/.kiro/skills/web-browse/SKILL.md +60 -60
- package/CHANGELOG.md +23 -0
- package/README.md +23 -21
- package/lib/state/workflow-state.js +217 -28
- package/package.json +4 -4
- package/scripts/generate-docs.js +33 -5
- package/scripts/setup-hooks.js +0 -14
- package/site/content.json +40 -14
- package/site/index.html +23 -20
- package/site/ux-spec.md +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [5.8.5] - 2026-04-23
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **Hardcoded developer paths in web-ctl skills** (#333) - replaced 76 occurrences of `/Users/avifen/.agentsys/plugins/web-ctl/scripts/web-ctl.js` with `~/.agentsys/...` across `.kiro/skills/web-auth/SKILL.md` (16 sites) and `.kiro/skills/web-browse/SKILL.md` (60 sites). The original absolute path only existed on the maintainer's machine, so every CLI example silently failed for any other user. The portable form matches the install path documented in `meta/skills/maintain-cross-platform/SKILL.md` and works for both shell copy-paste and agent execution (Bash tool's `bash -c` performs tilde expansion).
|
|
16
|
+
- **`prepare` lifecycle hook auto-installed git hooks on every `npm install`** (#334) - moved hook installation from npm's `prepare` script to an explicit `setup-hooks` script so consumers no longer get hooks injected as a side effect of `npm install`. Documented opt-in flow in `CONTRIBUTING.md`. Also removed the no-op pre-commit placeholder (it just wrote a comment file - lib/ sync is handled by agent-core CI now), so only the actually-active pre-push hook (preflight + `/enhance` reminder + release-tag validation) is installed.
|
|
17
|
+
- **`npm version` lifecycle dropped downstream version stamps** (#339, #342) - replaced `git add -A` (which would sweep unrelated working-tree changes into the version commit) with an explicit allowlist covering every file `stamp-version.js` writes plus npm's own lockfile and `CHANGELOG.md`: `package.json`, `package-lock.json`, `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json`, `site/content.json`, `CHANGELOG.md`. Preserves the original intent (no working-tree sweep) while keeping all version manifests consistent after `npm version`. (CHANGELOG.md added per gemini-code-assist review on #342 - the developer manually edits CHANGELOG before each release, so it must be in the allowlist or `npm version`'s auto-commit drops the changelog entry.)
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **`js-yaml` dependency range tightened from `^4.1.1` to `~4.1.1`** (#335) - blocks unintended `4.x` minor bumps while still allowing `4.1.x` patch updates so runtime security fixes flow in automatically. Lockfile root entry synced to match.
|
|
21
|
+
|
|
22
|
+
## [5.8.4] - 2026-04-20
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **tasks.json atomic optimistic locking** (#331) - Concurrent `/next-task` and `/ship` runs could silently lose claims or leave stale registry entries due to unguarded read-modify-write on `tasks.json`. Fix uses `_version` + per-write `_writerId` optimistic locking (mirrors existing `flow.json` pattern): write atomically via rename, re-read and verify both fields match before declaring success, retry up to 5× with jitter on mismatch.
|
|
26
|
+
- **tasks.json schema unification** - `worktree-manager` wrote `{ version, tasks[] }` while `workflow-state.js` read `{ active }`, causing claim exclusion in `discover-tasks` to always return an empty set. Unified schema is `{ active, tasks[], _version, _writerId }` with on-read normalization of both legacy formats — no migration needed.
|
|
27
|
+
- **Silent corruption risk** - `readTasks()` now throws on corrupted JSON instead of returning a safe default, preventing `updateTasks` from silently overwriting potentially recoverable data.
|
|
28
|
+
- **Agent prompt raw file writes** - `worktree-manager` Phase 6 and Cleanup Reference replaced inline `fs.writeFileSync` with `workflowState.claimTask()` / `workflowState.releaseTask()` library calls that are atomic and retry-safe.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- `updateTasks(mutatorFn)` - optimistic-lock loop for `tasks.json` mutations (mirrors `updateFlow`)
|
|
32
|
+
- `claimTask(entry, projectPath)` - atomic upsert into `tasks[]` registry for worktree-manager
|
|
33
|
+
- `releaseTask(taskId, projectPath)` - atomic removal from `tasks[]` registry for ship/abort; idempotent
|
|
34
|
+
|
|
12
35
|
## [5.8.3] - 2026-04-11
|
|
13
36
|
|
|
14
37
|
### Fixed
|
package/README.md
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
21
|
<p align="center">
|
|
22
|
-
<b>19 plugins ·
|
|
23
|
-
<em>Plugins distributed as standalone repos under <a href="https://github.com/agent-sh">agent-sh</a> org
|
|
22
|
+
<b>19 plugins · 49 agents · 41 skills (across all repos) · 30k lines of lib code · 3,507 tests · 5 platforms</b><br>
|
|
23
|
+
<em>Plugins distributed as standalone repos under <a href="https://github.com/agent-sh">agent-sh</a> org - agentsys is the marketplace & installer</em>
|
|
24
24
|
</p>
|
|
25
25
|
|
|
26
26
|
<p align="center">
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
AI models can write code. That's not the hard part anymore. The hard part is everything around it
|
|
41
|
+
AI models can write code. That's not the hard part anymore. The hard part is everything around it - task selection, branch management, code review, artifact cleanup, CI, PR comments, deployment. **AgentSys is the runtime that orchestrates agents to handle all of it** - structured pipelines, gated phases, specialized agents, and persistent state that survives session boundaries.
|
|
42
42
|
|
|
43
43
|
---
|
|
44
|
-
> Building custom skills, agents, hooks, or MCP tools? [agnix](https://github.com/agent-sh/agnix) is the CLI + LSP linter that catches config errors before they fail silently - real-time IDE validation, auto suggestions, auto-fix, and
|
|
44
|
+
> Building custom skills, agents, hooks, or MCP tools? [agnix](https://github.com/agent-sh/agnix) is the CLI + LSP linter that catches config errors before they fail silently - real-time IDE validation, auto suggestions, auto-fix, and 399 rules for Claude Code, Codex, OpenCode, Cursor, Kiro, Copilot, Gemini CLI, Cline, Windsurf, Roo Code, Amp, and more.
|
|
45
45
|
|
|
46
46
|
## What This Is
|
|
47
47
|
|
|
48
|
-
An agent orchestration system
|
|
48
|
+
An agent orchestration system - 19 plugins, 49 agents (39 file-based + 10 role-based specialists in audit-project), and 41 skills that compose into structured pipelines for software development. Each plugin lives in its own standalone repo under the [agent-sh](https://github.com/agent-sh) org. agentsys is the marketplace and installer that ties them together.
|
|
49
49
|
|
|
50
50
|
Each agent has a single responsibility, a specific model assignment, and defined inputs/outputs. Pipelines enforce phase gates so agents can't skip steps. State persists across sessions so work survives interruptions.
|
|
51
51
|
|
|
@@ -57,8 +57,8 @@ The system runs on Claude Code, OpenCode, Codex CLI, Cursor, and Kiro. Install v
|
|
|
57
57
|
|
|
58
58
|
**Code does code work. AI does AI work.**
|
|
59
59
|
|
|
60
|
-
- **Detection**: regex, AST analysis, static analysis
|
|
61
|
-
- **Judgment**: LLM calls for synthesis, planning, review
|
|
60
|
+
- **Detection**: regex, AST analysis, static analysis - fast, deterministic, no tokens wasted
|
|
61
|
+
- **Judgment**: LLM calls for synthesis, planning, review - where reasoning matters
|
|
62
62
|
- **Result**: 77% fewer tokens for [/drift-detect](#drift-detect) vs multi-agent approaches, certainty-graded findings throughout
|
|
63
63
|
|
|
64
64
|
**Certainty levels exist because not all findings are equal:**
|
|
@@ -118,7 +118,7 @@ The investment shifts from model spend to pipeline design. Better prompts, riche
|
|
|
118
118
|
| [`/next-task`](#next-task) | Task workflow: discovery, implementation, PR, merge |
|
|
119
119
|
| [`/prepare-delivery`](#prepare-delivery) | Pre-ship quality gates: deslop, review, validation, docs sync |
|
|
120
120
|
| [`/gate-and-ship`](#gate-and-ship) | Quality gates then ship (/prepare-delivery + /ship) |
|
|
121
|
-
| [`/agnix`](#agnix) | Lint agent configurations (
|
|
121
|
+
| [`/agnix`](#agnix) | Lint agent configurations (399 rules) |
|
|
122
122
|
| [`/ship`](#ship) | PR creation, CI monitoring, merge |
|
|
123
123
|
| [`/deslop`](#deslop) | Clean AI slop patterns |
|
|
124
124
|
| [`/perf`](#perf) | Performance investigation with baselines and profiling |
|
|
@@ -142,7 +142,7 @@ Each command works standalone. Together, they compose into end-to-end pipelines.
|
|
|
142
142
|
|
|
143
143
|
## Skills
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
41 skills included across the plugins:
|
|
146
146
|
|
|
147
147
|
| Category | Skills |
|
|
148
148
|
|----------|--------|
|
|
@@ -157,6 +157,7 @@ Each command works standalone. Together, they compose into end-to-end pipelines.
|
|
|
157
157
|
| **Web** | `web-auth`, `web-browse` |
|
|
158
158
|
| **Release** | `release` |
|
|
159
159
|
| **Analysis** | `drift-analysis`, `repo-intel` |
|
|
160
|
+
| **Linting** | `agnix` |
|
|
160
161
|
|
|
161
162
|
**External skill plugins** (standalone repos, installed separately):
|
|
162
163
|
|
|
@@ -175,7 +176,7 @@ Skills are the reusable implementation units. Agents invoke skills; commands orc
|
|
|
175
176
|
| [The Approach](#the-approach) | Why it's built this way |
|
|
176
177
|
| [Benchmarks](#benchmarks) | Sonnet + agentsys vs raw Opus |
|
|
177
178
|
| [Commands](#commands) | All 20 commands overview |
|
|
178
|
-
| [Skills](#skills) |
|
|
179
|
+
| [Skills](#skills) | 41 skills across plugins |
|
|
179
180
|
| [Skill-Only Plugins](#skill-only-plugins) | glide-mq and other non-command plugins |
|
|
180
181
|
| [Command Details](#command-details) | Deep dive into each command |
|
|
181
182
|
| [How Commands Work Together](#how-commands-work-together) | Standalone vs integrated |
|
|
@@ -328,7 +329,7 @@ agnix catches these issues before they cause problems.
|
|
|
328
329
|
| **Best Practices** | Tool restrictions, model selection, trigger phrase quality |
|
|
329
330
|
| **Cross-Platform** | Compatibility across Claude Code, Codex, OpenCode, Cursor, Kiro, Copilot, Gemini CLI, Cline, Windsurf, Roo Code, Amp, and more |
|
|
330
331
|
|
|
331
|
-
**
|
|
332
|
+
**399 validation rules** (126 auto-fixable) derived from:
|
|
332
333
|
- Official tool specifications (Claude Code, Codex CLI, OpenCode, Cursor, Kiro, GitHub Copilot, Gemini CLI, Cline, Windsurf, Roo Code, Amp, and more)
|
|
333
334
|
- Research papers on agent reliability and prompt injection
|
|
334
335
|
- Real-world testing across 500+ repositories
|
|
@@ -442,7 +443,7 @@ If something can't be fixed, the workflow replies explaining why and resolves th
|
|
|
442
443
|
|
|
443
444
|
### /deslop
|
|
444
445
|
|
|
445
|
-
**Purpose:** Finds AI slop
|
|
446
|
+
**Purpose:** Finds AI slop - debug statements, placeholder text, verbose comments, TODOs - and removes it.
|
|
446
447
|
|
|
447
448
|
**How detection works:**
|
|
448
449
|
|
|
@@ -613,13 +614,14 @@ Findings are collected and categorized by severity (critical/high/medium/low). A
|
|
|
613
614
|
|
|
614
615
|
**Purpose:** Analyzes your prompts, plugins, agents, docs, hooks, and skills for improvement opportunities.
|
|
615
616
|
|
|
616
|
-
**
|
|
617
|
+
**Eight analyzers run in parallel:**
|
|
617
618
|
|
|
618
619
|
| Analyzer | What it checks |
|
|
619
620
|
|----------|----------------|
|
|
620
621
|
| plugin-enhancer | Plugin structure, MCP tool definitions, security patterns |
|
|
621
622
|
| agent-enhancer | Agent frontmatter, prompt quality |
|
|
622
623
|
| claudemd-enhancer | CLAUDE.md/AGENTS.md structure, token efficiency |
|
|
624
|
+
| cross-file-enhancer | Cross-file consistency (tools vs frontmatter, duplicate rules, conflicts) |
|
|
623
625
|
| docs-enhancer | Documentation readability, RAG optimization |
|
|
624
626
|
| prompt-enhancer | Prompt engineering patterns, clarity, examples |
|
|
625
627
|
| hooks-enhancer | Hook frontmatter, structure, safety |
|
|
@@ -656,7 +658,7 @@ Findings are collected and categorized by severity (critical/high/medium/low). A
|
|
|
656
658
|
- AST symbol mapping: exports, functions, classes, imports
|
|
657
659
|
- Project metadata and health metrics
|
|
658
660
|
|
|
659
|
-
Output is cached at `{state-dir}/repo-intel.json` and `{state-dir}/repo-map.json`.
|
|
661
|
+
Output is cached at `{state-dir}/repo-intel.json` (external repo-intel plugin) and `{state-dir}/repo-map.json` (agentsys internal repo-map library). `{state-dir}` is `.claude/`, `.opencode/`, or `.codex/` depending on your platform.
|
|
660
662
|
|
|
661
663
|
**Why it matters:**
|
|
662
664
|
|
|
@@ -678,7 +680,7 @@ Backed by [agent-analyzer](https://github.com/agent-sh/agent-analyzer) Rust bina
|
|
|
678
680
|
|
|
679
681
|
### /sync-docs
|
|
680
682
|
|
|
681
|
-
**Purpose:** Sync documentation with actual code changes
|
|
683
|
+
**Purpose:** Sync documentation with actual code changes - find outdated refs, update CHANGELOG, flag stale examples.
|
|
682
684
|
|
|
683
685
|
**The problem it solves:**
|
|
684
686
|
|
|
@@ -958,7 +960,7 @@ No per-turn overhead - it reads transcripts that Claude Code already saves.
|
|
|
958
960
|
**What happens when you run it:**
|
|
959
961
|
|
|
960
962
|
1. **Collect** (68ms median) - Pure JavaScript scans manifest, structure, README, CI, git info. Normal depth adds CLAUDE.md/AGENTS.md and repo-intel. No LLM tokens.
|
|
961
|
-
2. **Synthesize** -
|
|
963
|
+
2. **Synthesize** - Sonnet agent produces a structured overview: tech stack, key files, active areas, conventions
|
|
962
964
|
3. **Guide** - Interactive Q&A: ask about specific files, areas, or patterns
|
|
963
965
|
|
|
964
966
|
**74% fewer tokens** than manual onboarding. Validated on 100 repos across JS/TS, Rust, Go, Python, C/C++, Java, and Deno.
|
|
@@ -981,7 +983,7 @@ No per-turn overhead - it reads transcripts that Claude Code already saves.
|
|
|
981
983
|
/onboard --depth=deep # Include AST data
|
|
982
984
|
```
|
|
983
985
|
|
|
984
|
-
**Agent:** onboard-agent (
|
|
986
|
+
**Agent:** onboard-agent (sonnet model)
|
|
985
987
|
|
|
986
988
|
[Full documentation →](https://github.com/agent-sh/onboard)
|
|
987
989
|
|
|
@@ -994,7 +996,7 @@ No per-turn overhead - it reads transcripts that Claude Code already saves.
|
|
|
994
996
|
**What happens when you run it:**
|
|
995
997
|
|
|
996
998
|
1. **Collect** - Gathers project data + contributor signals (test gaps, doc drift, bugspots, good-first areas, open issues). Validated on 100 repos.
|
|
997
|
-
2. **Match** -
|
|
999
|
+
2. **Match** - Sonnet agent asks about developer background and matches skills to project needs
|
|
998
1000
|
3. **Guide** - For each recommendation: reads code, explains what needs doing, gives a concrete first step
|
|
999
1001
|
|
|
1000
1002
|
**Matching:**
|
|
@@ -1015,7 +1017,7 @@ No per-turn overhead - it reads transcripts that Claude Code already saves.
|
|
|
1015
1017
|
/can-i-help --depth=deep # Include AST data
|
|
1016
1018
|
```
|
|
1017
1019
|
|
|
1018
|
-
**Agent:** can-i-help-agent (
|
|
1020
|
+
**Agent:** can-i-help-agent (sonnet model)
|
|
1019
1021
|
|
|
1020
1022
|
[Full documentation →](https://github.com/agent-sh/can-i-help)
|
|
1021
1023
|
|
|
@@ -1092,7 +1094,7 @@ Same principle as good code: single responsibility. The exploration-agent explor
|
|
|
1092
1094
|
|
|
1093
1095
|
**2. Pipeline with gates, not a monolith**
|
|
1094
1096
|
|
|
1095
|
-
Same principle as DevOps. Each step must pass before the next begins. Can't push before review. Can't merge before CI passes. Hooks enforce this
|
|
1097
|
+
Same principle as DevOps. Each step must pass before the next begins. Can't push before review. Can't merge before CI passes. Hooks enforce this - agents literally cannot skip phases.
|
|
1096
1098
|
|
|
1097
1099
|
**3. Tools do tool work, agents do agent work**
|
|
1098
1100
|
|
|
@@ -1235,7 +1237,7 @@ The system is built on research, not guesswork.
|
|
|
1235
1237
|
- Instruction following reliability
|
|
1236
1238
|
|
|
1237
1239
|
**Testing:**
|
|
1238
|
-
- 3,
|
|
1240
|
+
- 3,507 tests passing
|
|
1239
1241
|
- Drift-detect validated on 1,000+ repositories
|
|
1240
1242
|
- E2E workflow testing across all commands
|
|
1241
1243
|
- Cross-platform validation (Claude Code, OpenCode, Codex CLI, Cursor, Kiro)
|
|
@@ -117,62 +117,248 @@ function getTasksPath(projectPath = process.cwd()) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
* Read tasks.json from main project
|
|
121
|
-
*
|
|
122
|
-
*
|
|
120
|
+
* Read tasks.json from main project.
|
|
121
|
+
*
|
|
122
|
+
* Unified schema (v2):
|
|
123
|
+
* { active: null|Object, tasks: [], _version: number }
|
|
124
|
+
*
|
|
125
|
+
* - active: single active workflow entry (set by createFlow / cleared by completeWorkflow)
|
|
126
|
+
* - tasks: worktree claim registry (set by worktree-manager / cleared by ship or --abort)
|
|
127
|
+
* - _version: monotonic counter for optimistic locking (managed by writeTasks)
|
|
128
|
+
* - _writerId: per-write unique token used by updateTasks to detect concurrent wins
|
|
129
|
+
*
|
|
130
|
+
* Legacy formats are normalized on read — no migration script needed.
|
|
131
|
+
* Throws on corruption so callers can decide whether to abort or recover,
|
|
132
|
+
* rather than silently overwriting potentially recoverable data.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} projectPath
|
|
135
|
+
* @returns {{ active: null|Object, tasks: Array, _version: number, _writerId?: string }}
|
|
136
|
+
* @throws {Error} If tasks.json exists but cannot be parsed
|
|
123
137
|
*/
|
|
124
138
|
function readTasks(projectPath = process.cwd()) {
|
|
125
139
|
const tasksPath = getTasksPath(projectPath);
|
|
126
140
|
if (!fs.existsSync(tasksPath)) {
|
|
127
|
-
return { active: null };
|
|
141
|
+
return { active: null, tasks: [], _version: 0 };
|
|
128
142
|
}
|
|
143
|
+
const raw = fs.readFileSync(tasksPath, 'utf8');
|
|
144
|
+
let data;
|
|
129
145
|
try {
|
|
130
|
-
|
|
131
|
-
// Normalize legacy format that may not have 'active' field
|
|
132
|
-
if (!Object.prototype.hasOwnProperty.call(data, 'active')) {
|
|
133
|
-
return { active: null };
|
|
134
|
-
}
|
|
135
|
-
return data;
|
|
146
|
+
data = JSON.parse(raw);
|
|
136
147
|
} catch (e) {
|
|
137
|
-
|
|
138
|
-
return { active: null };
|
|
148
|
+
throw new Error(`[CRITICAL] Corrupted tasks.json at ${tasksPath}: ${e.message}. File must be repaired or deleted manually before writes are allowed.`);
|
|
139
149
|
}
|
|
150
|
+
// Normalize: ensure every field exists (handles legacy { active } and legacy { version, tasks[] })
|
|
151
|
+
return {
|
|
152
|
+
active: Object.prototype.hasOwnProperty.call(data, 'active') ? data.active : null,
|
|
153
|
+
tasks: Array.isArray(data.tasks) ? data.tasks : [],
|
|
154
|
+
_version: typeof data._version === 'number' ? data._version : 0,
|
|
155
|
+
_writerId: typeof data._writerId === 'string' ? data._writerId : undefined
|
|
156
|
+
};
|
|
140
157
|
}
|
|
141
158
|
|
|
142
159
|
/**
|
|
143
|
-
* Write tasks.json
|
|
160
|
+
* Write tasks.json atomically.
|
|
161
|
+
* Increments _version and stamps a unique _writerId per write.
|
|
162
|
+
*
|
|
163
|
+
* Both fields are used by updateTasks to verify it was the winning writer:
|
|
164
|
+
* if two processes both read _version N, both write _version N+1 (last
|
|
165
|
+
* renameSync wins), the loser re-reads and finds a _writerId that does not
|
|
166
|
+
* match its own — it knows it lost and retries.
|
|
167
|
+
*
|
|
168
|
+
* @returns {string} The _writerId stamped into this write
|
|
144
169
|
*/
|
|
145
170
|
function writeTasks(tasks, projectPath = process.cwd()) {
|
|
146
171
|
ensureStateDir(projectPath);
|
|
172
|
+
const copy = structuredClone(tasks);
|
|
173
|
+
copy._version = (copy._version || 0) + 1;
|
|
174
|
+
copy._writerId = crypto.randomBytes(8).toString('hex');
|
|
147
175
|
const tasksPath = getTasksPath(projectPath);
|
|
148
|
-
writeJsonAtomic(tasksPath,
|
|
149
|
-
return
|
|
176
|
+
writeJsonAtomic(tasksPath, copy);
|
|
177
|
+
return copy._writerId;
|
|
150
178
|
}
|
|
151
179
|
|
|
152
180
|
/**
|
|
153
|
-
*
|
|
181
|
+
* Apply a mutation to tasks.json with optimistic locking.
|
|
182
|
+
*
|
|
183
|
+
* Uses _version + _writerId to detect wins in concurrent-writer races:
|
|
184
|
+
* 1. Read current state, snapshot _version
|
|
185
|
+
* 2. Apply mutatorFn(clone) → new state; skip write if state unchanged
|
|
186
|
+
* 3. Stamp a unique writerId, write atomically (increments _version)
|
|
187
|
+
* 4. Re-read: if _version === initialVersion + 1 AND _writerId matches → we won
|
|
188
|
+
* 5. Otherwise another writer raced us → back off with jitter, retry
|
|
189
|
+
*
|
|
190
|
+
* @param {function(Object): Object} mutatorFn - Pure function that receives a
|
|
191
|
+
* deep clone of current tasks state and returns the desired new state.
|
|
192
|
+
* Must not have side effects; may be called multiple times on retry.
|
|
193
|
+
* @param {string} projectPath
|
|
194
|
+
* @returns {boolean} true on success, false after MAX_RETRIES exhausted or on corruption
|
|
195
|
+
*/
|
|
196
|
+
function updateTasks(mutatorFn, projectPath = process.cwd()) {
|
|
197
|
+
const MAX_RETRIES = 5;
|
|
198
|
+
|
|
199
|
+
let current;
|
|
200
|
+
try {
|
|
201
|
+
current = readTasks(projectPath);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.error(`[ERROR] updateTasks: cannot read tasks.json — ${e.message}`);
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
208
|
+
const initialVersion = current._version || 0;
|
|
209
|
+
|
|
210
|
+
let updated;
|
|
211
|
+
try {
|
|
212
|
+
updated = mutatorFn(structuredClone(current));
|
|
213
|
+
} catch (e) {
|
|
214
|
+
console.error(`[ERROR] updateTasks: mutatorFn threw on attempt ${attempt + 1}: ${e.message}`);
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Skip write if mutatorFn made no changes — avoids spurious version bumps
|
|
219
|
+
if (JSON.stringify(updated) === JSON.stringify(current)) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Carry forward the pre-write version so writeTasks increments it by exactly 1
|
|
224
|
+
updated._version = initialVersion;
|
|
225
|
+
|
|
226
|
+
const writerId = writeTasks(updated, projectPath);
|
|
227
|
+
|
|
228
|
+
// Verify we won: version must be exactly initialVersion + 1 AND writerId must match ours.
|
|
229
|
+
// If another process also wrote _version: initialVersion + 1, only one writerId survives.
|
|
230
|
+
let afterWrite;
|
|
231
|
+
try {
|
|
232
|
+
afterWrite = readTasks(projectPath);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
console.error(`[ERROR] updateTasks: tasks.json corrupted after write on attempt ${attempt + 1}: ${e.message}`);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (afterWrite._version === initialVersion + 1 && afterWrite._writerId === writerId) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Lost the race — retry from the current on-disk state
|
|
243
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
244
|
+
const delay = Math.floor(Math.random() * 50) + 10;
|
|
245
|
+
sleepForRetry(delay);
|
|
246
|
+
try {
|
|
247
|
+
current = readTasks(projectPath);
|
|
248
|
+
} catch (e) {
|
|
249
|
+
console.error(`[ERROR] updateTasks: tasks.json corrupted during retry ${attempt + 1}: ${e.message}`);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const tasksPath = getTasksPath(projectPath);
|
|
256
|
+
let lastVersion = '(unreadable)';
|
|
257
|
+
try { lastVersion = readTasks(projectPath)._version; } catch {}
|
|
258
|
+
|
|
259
|
+
// Final fallback: if a concurrent writer happened to apply the exact same
|
|
260
|
+
// mutation (idempotent operations like releaseTask on an already-absent entry),
|
|
261
|
+
// treat the outcome as a success rather than reporting a spurious failure.
|
|
262
|
+
let latest;
|
|
263
|
+
try { latest = readTasks(projectPath); } catch {}
|
|
264
|
+
if (latest) {
|
|
265
|
+
// Re-run the mutator on what's on disk; if the result is identical to
|
|
266
|
+
// what's already there, our desired state is already achieved.
|
|
267
|
+
try {
|
|
268
|
+
const wouldBe = mutatorFn(structuredClone(latest));
|
|
269
|
+
// Normalize _version/_writerId before comparing content
|
|
270
|
+
wouldBe._version = latest._version;
|
|
271
|
+
wouldBe._writerId = latest._writerId;
|
|
272
|
+
if (JSON.stringify(wouldBe) === JSON.stringify(latest)) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
} catch {}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.error(
|
|
279
|
+
`[ERROR] updateTasks: all ${MAX_RETRIES} attempts failed due to concurrent writers on ${tasksPath}. ` +
|
|
280
|
+
`Another agent process is modifying the registry simultaneously. ` +
|
|
281
|
+
`Last known _version: ${lastVersion}. ` +
|
|
282
|
+
`Suggested recovery: wait for the competing process to finish, then retry the operation.`
|
|
283
|
+
);
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Set active task in main project (uses optimistic locking)
|
|
154
289
|
*/
|
|
155
290
|
function setActiveTask(task, projectPath = process.cwd()) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
};
|
|
161
|
-
return writeTasks(tasks, projectPath);
|
|
291
|
+
return updateTasks(tasks => {
|
|
292
|
+
tasks.active = { ...task, startedAt: new Date().toISOString() };
|
|
293
|
+
return tasks;
|
|
294
|
+
}, projectPath);
|
|
162
295
|
}
|
|
163
296
|
|
|
164
297
|
/**
|
|
165
|
-
* Clear active task
|
|
298
|
+
* Clear active task (uses optimistic locking)
|
|
166
299
|
*/
|
|
167
300
|
function clearActiveTask(projectPath = process.cwd()) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
301
|
+
return updateTasks(tasks => {
|
|
302
|
+
tasks.active = null;
|
|
303
|
+
return tasks;
|
|
304
|
+
}, projectPath);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Claim a task in the registry (uses optimistic locking).
|
|
309
|
+
* Used by worktree-manager; replaces the raw fs.writeFileSync inline in agent prompts.
|
|
310
|
+
*
|
|
311
|
+
* @param {Object} entry - { id, source, title, branch, worktreePath, claimedBy }
|
|
312
|
+
* @param {string} projectPath
|
|
313
|
+
*/
|
|
314
|
+
function claimTask(entry, projectPath = process.cwd()) {
|
|
315
|
+
if (!entry || !entry.id) {
|
|
316
|
+
console.error('[ERROR] claimTask: entry.id is required');
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return updateTasks(tasks => {
|
|
320
|
+
const idx = tasks.tasks.findIndex(t => t.id === entry.id);
|
|
321
|
+
const record = {
|
|
322
|
+
...entry,
|
|
323
|
+
status: 'claimed',
|
|
324
|
+
claimedAt: entry.claimedAt || new Date().toISOString(),
|
|
325
|
+
lastActivityAt: new Date().toISOString()
|
|
326
|
+
};
|
|
327
|
+
if (idx >= 0) {
|
|
328
|
+
tasks.tasks[idx] = record;
|
|
329
|
+
} else {
|
|
330
|
+
tasks.tasks.push(record);
|
|
331
|
+
}
|
|
332
|
+
return tasks;
|
|
333
|
+
}, projectPath);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Release a claimed task from the registry (uses optimistic locking).
|
|
338
|
+
* Used by ship and --abort; replaces the raw fs.writeFileSync inline cleanup.
|
|
339
|
+
*
|
|
340
|
+
* @param {string} taskId
|
|
341
|
+
* @param {string} projectPath
|
|
342
|
+
*/
|
|
343
|
+
function releaseTask(taskId, projectPath = process.cwd()) {
|
|
344
|
+
if (!taskId) {
|
|
345
|
+
console.error('[ERROR] releaseTask: taskId is required');
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
return updateTasks(tasks => {
|
|
349
|
+
const before = tasks.tasks.length;
|
|
350
|
+
tasks.tasks = tasks.tasks.filter(t => t.id !== taskId);
|
|
351
|
+
if (tasks.tasks.length === before) {
|
|
352
|
+
// Not found — that's fine, idempotent
|
|
353
|
+
console.error(`[WARN] releaseTask: task ${taskId} was not found in tasks.json registry. It may have already been released or never claimed.`);
|
|
354
|
+
}
|
|
355
|
+
return tasks;
|
|
356
|
+
}, projectPath);
|
|
171
357
|
}
|
|
172
358
|
|
|
173
359
|
/**
|
|
174
|
-
* Check if there's an active task
|
|
175
|
-
* Uses != null to catch both null and undefined (legacy format safety)
|
|
360
|
+
* Check if there's an active task.
|
|
361
|
+
* Uses != null to catch both null and undefined (legacy format safety).
|
|
176
362
|
*/
|
|
177
363
|
function hasActiveTask(projectPath = process.cwd()) {
|
|
178
364
|
const tasks = readTasks(projectPath);
|
|
@@ -548,8 +734,11 @@ module.exports = {
|
|
|
548
734
|
getTasksPath,
|
|
549
735
|
readTasks,
|
|
550
736
|
writeTasks,
|
|
737
|
+
updateTasks,
|
|
551
738
|
setActiveTask,
|
|
552
739
|
clearActiveTask,
|
|
740
|
+
claimTask,
|
|
741
|
+
releaseTask,
|
|
553
742
|
hasActiveTask,
|
|
554
743
|
|
|
555
744
|
// Flow (worktree)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentsys",
|
|
3
|
-
"version": "5.8.
|
|
3
|
+
"version": "5.8.5",
|
|
4
4
|
"description": "A modular runtime and orchestration system for AI agents - works with Claude Code, OpenCode, and Codex CLI",
|
|
5
5
|
"main": "lib/platform/detect-platform.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"bump": "node bin/dev-cli.js bump",
|
|
38
38
|
"detect": "node bin/dev-cli.js detect",
|
|
39
39
|
"verify": "node bin/dev-cli.js verify",
|
|
40
|
-
"version": "node scripts/stamp-version.js && git add -
|
|
41
|
-
"
|
|
40
|
+
"version": "node scripts/stamp-version.js && git add package.json package-lock.json .claude-plugin/plugin.json .claude-plugin/marketplace.json site/content.json CHANGELOG.md",
|
|
41
|
+
"setup-hooks": "node bin/dev-cli.js setup-hooks"
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
84
|
"agentsys": "^5.0.0",
|
|
85
|
-
"js-yaml": "
|
|
85
|
+
"js-yaml": "~4.1.1"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"jest": "^29.7.0"
|
package/scripts/generate-docs.js
CHANGED
|
@@ -109,7 +109,8 @@ const STATIC_SKILLS = [
|
|
|
109
109
|
{ plugin: 'audit-project', name: 'audit-project' },
|
|
110
110
|
{ plugin: 'glidemq', name: 'glide-mq' },
|
|
111
111
|
{ plugin: 'glidemq', name: 'glide-mq-migrate-bullmq' },
|
|
112
|
-
{ plugin: 'glidemq', name: 'glide-mq-migrate-bee' }
|
|
112
|
+
{ plugin: 'glidemq', name: 'glide-mq-migrate-bee' },
|
|
113
|
+
{ plugin: 'agnix', name: 'agnix' }
|
|
113
114
|
];
|
|
114
115
|
|
|
115
116
|
// Purpose mapping for architecture table
|
|
@@ -192,7 +193,7 @@ function generateCommandsTable(commands) {
|
|
|
192
193
|
'next-task': 'Task workflow: discovery, implementation, PR, merge',
|
|
193
194
|
'prepare-delivery': 'Pre-ship quality gates: deslop, review, validation, docs sync',
|
|
194
195
|
'gate-and-ship': 'Quality gates then ship (/prepare-delivery + /ship)',
|
|
195
|
-
'agnix': 'Lint agent configurations (
|
|
196
|
+
'agnix': 'Lint agent configurations (399 rules)',
|
|
196
197
|
'ship': 'PR creation, CI monitoring, merge',
|
|
197
198
|
'deslop': 'Clean AI slop patterns',
|
|
198
199
|
'perf': 'Performance investigation with baselines and profiling',
|
|
@@ -395,9 +396,34 @@ function generateAgentCounts(agents, plugins) {
|
|
|
395
396
|
/**
|
|
396
397
|
* Update counts in site/content.json programmatically.
|
|
397
398
|
*/
|
|
398
|
-
// Static counts for cross-repo plugins not discoverable locally
|
|
399
|
-
|
|
400
|
-
|
|
399
|
+
// Static counts for cross-repo plugins not discoverable locally.
|
|
400
|
+
// Per-plugin file-based agent counts. Update this map when agents are added/removed
|
|
401
|
+
// in a plugin repo - it's the canonical source for the STATIC_AGENT_COUNT fallback.
|
|
402
|
+
const STATIC_PLUGIN_AGENT_COUNTS = {
|
|
403
|
+
'next-task': 8,
|
|
404
|
+
'prepare-delivery': 3,
|
|
405
|
+
'gate-and-ship': 0,
|
|
406
|
+
'ship': 1,
|
|
407
|
+
'deslop': 1,
|
|
408
|
+
'audit-project': 0,
|
|
409
|
+
'drift-detect': 1,
|
|
410
|
+
'enhance': 8,
|
|
411
|
+
'sync-docs': 1,
|
|
412
|
+
'repo-intel': 1,
|
|
413
|
+
'perf': 6,
|
|
414
|
+
'learn': 1,
|
|
415
|
+
'agnix': 1,
|
|
416
|
+
'consult': 1,
|
|
417
|
+
'debate': 1,
|
|
418
|
+
'web-ctl': 1,
|
|
419
|
+
'skillers': 2,
|
|
420
|
+
'onboard': 1,
|
|
421
|
+
'can-i-help': 1
|
|
422
|
+
};
|
|
423
|
+
const STATIC_PLUGIN_COUNT = Object.keys(STATIC_PLUGIN_AGENT_COUNTS).length;
|
|
424
|
+
const STATIC_FILE_BASED_AGENT_COUNT = Object.values(STATIC_PLUGIN_AGENT_COUNTS).reduce((sum, count) => sum + count, 0);
|
|
425
|
+
// Total = file-based + role-based (audit-project specialists, spawned dynamically)
|
|
426
|
+
const STATIC_AGENT_COUNT = STATIC_FILE_BASED_AGENT_COUNT + ROLE_BASED_AGENT_COUNT;
|
|
401
427
|
|
|
402
428
|
function updateSiteContent(plugins, agents, skills) {
|
|
403
429
|
const contentPath = path.join(ROOT_DIR, 'site', 'content.json');
|
|
@@ -652,6 +678,8 @@ module.exports = {
|
|
|
652
678
|
PURPOSE_MAP,
|
|
653
679
|
ROLE_BASED_AGENT_COUNT,
|
|
654
680
|
STATIC_SKILLS,
|
|
681
|
+
STATIC_PLUGIN_AGENT_COUNTS,
|
|
655
682
|
STATIC_PLUGIN_COUNT,
|
|
683
|
+
STATIC_FILE_BASED_AGENT_COUNT,
|
|
656
684
|
STATIC_AGENT_COUNT
|
|
657
685
|
};
|
package/scripts/setup-hooks.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Setup git hooks for development
|
|
4
|
-
* - pre-commit: Placeholder (lib/ sync handled by agent-core CI)
|
|
5
4
|
* - pre-push: Runs preflight checks, /enhance reminder, release validation
|
|
6
5
|
*/
|
|
7
6
|
|
|
@@ -9,13 +8,8 @@ const fs = require('fs');
|
|
|
9
8
|
const path = require('path');
|
|
10
9
|
|
|
11
10
|
const hookDir = path.join(__dirname, '..', '.git', 'hooks');
|
|
12
|
-
const preCommitPath = path.join(hookDir, 'pre-commit');
|
|
13
11
|
const prePushPath = path.join(hookDir, 'pre-push');
|
|
14
12
|
|
|
15
|
-
const preCommitHook = `#!/bin/sh
|
|
16
|
-
# Pre-commit hook (lib/ sync now handled by agent-core)
|
|
17
|
-
`;
|
|
18
|
-
|
|
19
13
|
const prePushHook = `#!/bin/sh
|
|
20
14
|
# Pre-push validations:
|
|
21
15
|
# 1. Run preflight checks (validators + gap checks)
|
|
@@ -135,14 +129,6 @@ function main() {
|
|
|
135
129
|
return 0;
|
|
136
130
|
}
|
|
137
131
|
|
|
138
|
-
try {
|
|
139
|
-
fs.writeFileSync(preCommitPath, preCommitHook, { mode: 0o755 });
|
|
140
|
-
console.log('Git pre-commit hook installed');
|
|
141
|
-
} catch (err) {
|
|
142
|
-
// Non-fatal - might not have write permissions
|
|
143
|
-
console.warn('Could not install pre-commit hook:', err.message);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
132
|
try {
|
|
147
133
|
fs.writeFileSync(prePushPath, prePushHook, { mode: 0o755 });
|
|
148
134
|
console.log('Git pre-push hook installed (release tag validation)');
|