@jaybeeuu/agent-cortex 1.37.1 → 1.39.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/.agents/skills/style-versioning/SKILL.md +9 -8
- package/.github/workflows/ci.yml +190 -8
- package/AGENTS.md +27 -6
- package/CHANGELOG.md +72 -0
- package/README.md +156 -39
- package/agents/README.md +49 -13
- package/agents/plan/agent.md +51 -0
- package/agents/plan/claude/frontmatter.json +6 -0
- package/agents/plan/copilot/frontmatter.json +6 -0
- package/agents/plan/pi/frontmatter.json +6 -0
- package/agents/plan.agent.md +3 -0
- package/agents/ralph/agent.md +262 -0
- package/agents/ralph/claude/frontmatter.json +6 -0
- package/agents/ralph/claude/polling.md +31 -0
- package/agents/ralph/copilot/frontmatter.json +6 -0
- package/agents/ralph/copilot/polling.md +62 -0
- package/agents/ralph/pi/frontmatter.json +6 -0
- package/agents/ralph/pi/polling.md +74 -0
- package/agents/ralph-plan/agent.md +114 -0
- package/agents/ralph-plan/claude/frontmatter.json +6 -0
- package/agents/ralph-plan/copilot/frontmatter.json +6 -0
- package/agents/ralph-plan/pi/frontmatter.json +6 -0
- package/agents/ralph-plan.agent.md +6 -3
- package/agents/ralph.agent.md +83 -57
- package/agents/strategy/agent.md +104 -0
- package/agents/strategy/claude/frontmatter.json +6 -0
- package/agents/strategy/copilot/frontmatter.json +6 -0
- package/agents/strategy/pi/frontmatter.json +6 -0
- package/agents/strategy.agent.md +3 -0
- package/bin/agent-cortex.mjs +76 -1
- package/bin/installers/claude.mjs +376 -0
- package/bin/installers/copilot.mjs +129 -0
- package/bin/installers/pi.mjs +197 -0
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/agents/plan.md +3 -1
- package/claude/agents/ralph-plan.md +6 -4
- package/claude/agents/strategy.md +3 -1
- package/claude/hooks/scripts/notify.mjs +54 -0
- package/claude/hooks.json +15 -0
- package/docs/claude-hooks.md +115 -0
- package/docs/ideas/auto-model-selection.md +75 -0
- package/docs/ideas/extension-manifests.md +75 -0
- package/docs/ideas/improve-pi-system-prompt.md +34 -0
- package/docs/ideas/model-selector.md +40 -0
- package/docs/ideas/pi-task-memory.md +34 -0
- package/docs/ideas/qa-extension.md +43 -0
- package/docs/ideas/record-ideas-as-beads.md +115 -0
- package/docs/ideas/subagent-visibility.md +72 -0
- package/docs/ideas/task-session-context.md +89 -0
- package/docs/ideas/unified-ci-pipeline.md +28 -0
- package/docs/research/skill-design-research.md +4 -4
- package/docs/research/skill-improvements-analysis.md +1 -1
- package/docs/skills/skill-anatomy.md +47 -2
- package/docs/technical-direction/migration-execution-labels.md +132 -0
- package/extensions/agent-modes/README.md +54 -0
- package/extensions/agent-modes/discover.test.ts +241 -0
- package/extensions/agent-modes/discover.ts +458 -0
- package/extensions/agent-modes/index.ts +13 -224
- package/extensions/agent-modes/node_modules/.bin/pi +21 -0
- package/extensions/agent-modes/node_modules/.bin/tsc +21 -0
- package/extensions/agent-modes/node_modules/.bin/tsserver +21 -0
- package/extensions/agent-modes/node_modules/.bin/tsx +21 -0
- package/extensions/agent-modes/package.json +20 -0
- package/extensions/agent-modes/tsconfig.json +12 -0
- package/extensions/subagent/README.md +19 -0
- package/extensions/subagent/index.ts +131 -45
- package/extensions/subagent/node_modules/.bin/pi +21 -0
- package/extensions/subagent/node_modules/.bin/pi-ai +21 -0
- package/extensions/subagent/node_modules/.bin/tsc +21 -0
- package/extensions/subagent/node_modules/.bin/tsserver +21 -0
- package/extensions/subagent/node_modules/.bin/tsx +21 -0
- package/extensions/subagent/package.json +23 -0
- package/extensions/subagent/tsconfig.json +12 -0
- package/extensions/subagent/wait.test.ts +202 -0
- package/extensions/subagent/wait.ts +118 -0
- package/hooks/claude/hooks.json +38 -0
- package/hooks/claude/scripts/notify.mjs +54 -0
- package/lib/cli.mjs +68 -3
- package/package.json +6 -4
- package/plugin.json +1 -1
- package/pnpm-workspace.yaml +1 -1
- package/scripts/build-copilot-agents.mjs +30 -0
- package/scripts/lib/compose-agent.mjs +241 -0
- package/skills/engineering/design-an-interface/SKILL.md +78 -58
- package/skills/engineering/grill-with-docs/REFERENCE.md +78 -0
- package/skills/engineering/grill-with-docs/SKILL.md +66 -63
- package/skills/engineering/improve-codebase-architecture/SKILL.md +66 -48
- package/skills/engineering/technical-direction/FORMAT.md +38 -0
- package/skills/engineering/technical-direction/SKILL.md +70 -77
- package/skills/planning/classify-bead/SKILL.md +59 -35
- package/skills/planning/create-task/REFERENCE.md +40 -0
- package/skills/planning/create-task/SKILL.md +70 -66
- package/skills/planning/create-task/scripts/create-chores.ts +6 -1
- package/skills/planning/create-task/scripts/node_modules/.bin/tsc +21 -0
- package/skills/planning/create-task/scripts/node_modules/.bin/tsserver +21 -0
- package/skills/planning/create-task/scripts/node_modules/.bin/tsx +21 -0
- package/skills/planning/prd-to-tasks/REFERENCE.md +66 -0
- package/skills/planning/prd-to-tasks/SKILL.md +86 -170
- package/skills/planning/record-idea/FORMAT.md +44 -0
- package/skills/planning/record-idea/SKILL.md +70 -53
- package/skills/planning/request-refactor-plan/FORMAT.md +45 -0
- package/skills/planning/request-refactor-plan/SKILL.md +57 -46
- package/skills/planning/write-a-prd/FORMAT.md +54 -0
- package/skills/planning/write-a-prd/SKILL.md +49 -48
- package/skills/planning/write-a-ticket/SKILL.md +68 -53
- package/skills/productivity/handoff/SKILL.md +12 -0
- package/skills/productivity/init-beads/SKILL.md +48 -40
- package/skills/productivity/validate-agent-dir/scripts/node_modules/.bin/tsc +21 -0
- package/skills/productivity/validate-agent-dir/scripts/node_modules/.bin/tsserver +21 -0
- package/skills/productivity/validate-agent-dir/scripts/node_modules/.bin/tsx +21 -0
- package/skills/productivity/write-a-skill/SKILL.md +2 -1
- package/skills/review/maintain-agent-docs/SKILL.md +71 -51
- package/skills/workflow/hitl-collab/SKILL.md +86 -17
- package/skills/workflow/ralph/REFERENCE.md +24 -19
- package/skills/workflow/ralph/SKILL.md +88 -74
- package/skills/workflow/run-pipeline-stage/REFERENCE.md +22 -0
- package/skills/workflow/run-pipeline-stage/SKILL.md +91 -93
- package/skills/workflow/run-pipeline-stage/scripts/generate-progress.test.ts +6 -107
- package/skills/workflow/run-pipeline-stage/scripts/generate-progress.ts +4 -2
- package/skills/workflow/run-pipeline-stage/scripts/node_modules/.bin/tsc +21 -0
- package/skills/workflow/run-pipeline-stage/scripts/node_modules/.bin/tsserver +21 -0
- package/skills/workflow/run-pipeline-stage/scripts/node_modules/.bin/tsx +21 -0
- package/skills/workflow/using-agent-skills/SKILL.md +122 -0
- package/test/claude-installer.test.mjs +248 -0
- package/test/cli.test.mjs +115 -2
- package/test/copilot-installer.test.mjs +298 -0
- package/test/installer.test.mjs +179 -0
- package/test/notify-hook.test.mjs +63 -0
- package/test/pi-installer.test.mjs +371 -0
- package/token-map.README.md +130 -0
- package/token-map.json +106 -0
- package/.github/workflows/ci.test.ts +0 -217
- package/.github/workflows/release.yml +0 -58
- package/scripts/build-claude-agents.mjs +0 -179
- package/scripts/publish-release.sh +0 -32
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsc" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/typescript@5.9.3/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/tsx@4.22.4/node_modules/tsx/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/tsx@4.22.4/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/tsx@4.22.4/node_modules/tsx/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/tsx@4.22.4/node_modules:/home/runner/work/agent-cortex/agent-cortex/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../tsx/dist/cli.mjs" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../tsx/dist/cli.mjs" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: using-agent-skills
|
|
3
|
+
description: Routes incoming work to the right skill and enforces core agent behaviors. Use when starting a session, deciding which skill applies, or when you need to check "which skill should I use?".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Using Agent Skills
|
|
7
|
+
|
|
8
|
+
## When to use
|
|
9
|
+
|
|
10
|
+
- Starting a new session and need to orient on available workflows.
|
|
11
|
+
- A task arrives and you need to decide which skill applies.
|
|
12
|
+
- The user asks "which skill should I use for this?"
|
|
13
|
+
- You want to verify that core operating behaviors are being followed.
|
|
14
|
+
|
|
15
|
+
## Workflow
|
|
16
|
+
|
|
17
|
+
When a task arrives, identify the development phase and apply the corresponding skill:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Task arrives
|
|
21
|
+
│
|
|
22
|
+
├── Don't know what to build yet? ──────→ grill-me
|
|
23
|
+
├── Have a rough idea? ─────────────────→ record-idea
|
|
24
|
+
├── New feature / PRD needed? ──────────→ write-a-prd
|
|
25
|
+
├── PRD approved, need tasks? ──────────→ prd-to-tasks
|
|
26
|
+
├── Need a single task tracked? ────────→ create-task
|
|
27
|
+
├── Need to plan a feature end-to-end? ─→ plan
|
|
28
|
+
├── Running beads / task execution? ────→ ralph
|
|
29
|
+
│ └── Single stage? ─────────────────→ run-pipeline-stage
|
|
30
|
+
├── Implementing code? ────────────────→ tdd
|
|
31
|
+
│ ├── Designing an interface? ───────→ design-an-interface
|
|
32
|
+
│ └── Need style conventions? ───────→ style-code
|
|
33
|
+
├── Reviewing code? ───────────────────→ style-code
|
|
34
|
+
│ ├── Security concerns? ────────────→ review-security
|
|
35
|
+
│ └── Reviewing tests? ──────────────→ style-tests
|
|
36
|
+
├── Writing tests? ────────────────────→ style-tests
|
|
37
|
+
├── Writing docs? ─────────────────────→ style-documentation
|
|
38
|
+
├── Writing external comms? ───────────→ style-comms
|
|
39
|
+
├── Stress-testing a plan? ────────────→ grill-with-docs
|
|
40
|
+
├── Planning a refactor? ──────────────→ request-refactor-plan
|
|
41
|
+
├── Evaluating architecture? ──────────→ technical-direction
|
|
42
|
+
├── Finding refactor opportunities? ───→ improve-codebase-architecture
|
|
43
|
+
├── Need task tracking? ───────────────→ bd-tool
|
|
44
|
+
├── Need human review / HITL? ─────────→ hitl-collab
|
|
45
|
+
├── Need a handoff doc? ───────────────→ handoff
|
|
46
|
+
├── Writing a ticket for Jira? ────────→ write-a-ticket
|
|
47
|
+
├── Creating a new skill? ─────────────→ write-a-skill
|
|
48
|
+
├── Refactoring a skill? ──────────────→ refactor-skill
|
|
49
|
+
├── Updating agent docs? ──────────────→ maintain-agent-docs
|
|
50
|
+
└── Setting up beads in a new project? → init-beads
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Core Operating Behaviors
|
|
54
|
+
|
|
55
|
+
These behaviors apply at all times, across all skills. They are non-negotiable.
|
|
56
|
+
|
|
57
|
+
### 1. Surface Assumptions
|
|
58
|
+
|
|
59
|
+
Before implementing anything non-trivial, explicitly state your assumptions. Don't silently fill in ambiguous requirements — surface uncertainty early, it's cheaper than rework.
|
|
60
|
+
|
|
61
|
+
### 2. Manage Confusion Actively
|
|
62
|
+
|
|
63
|
+
When you encounter inconsistencies, conflicting requirements, or unclear specifications: **STOP**. Name the specific confusion, present the tradeoff or ask the clarifying question, and wait for resolution before continuing.
|
|
64
|
+
|
|
65
|
+
### 3. Push Back When Warranted
|
|
66
|
+
|
|
67
|
+
You are not a yes-machine. When an approach has clear problems, point out the issue directly, explain the concrete downside, propose an alternative, and accept the human's decision if they override with full information.
|
|
68
|
+
|
|
69
|
+
### 4. Enforce Simplicity
|
|
70
|
+
|
|
71
|
+
Your natural tendency is to overcomplicate. Before finishing any implementation, ask: Can this be done in fewer lines? Are these abstractions earning their complexity? Prefer the boring, obvious solution.
|
|
72
|
+
|
|
73
|
+
### 5. Maintain Scope Discipline
|
|
74
|
+
|
|
75
|
+
Touch only what you're asked to touch. Do not clean up orthogonal code, refactor adjacent systems, or add features not in the spec. Surgical precision, not unsolicited renovation.
|
|
76
|
+
|
|
77
|
+
### 6. Verify, Don't Assume
|
|
78
|
+
|
|
79
|
+
A task is not complete until verification passes. "Seems right" is never sufficient — there must be evidence: passing tests, build output, or runtime data.
|
|
80
|
+
|
|
81
|
+
### 7. Flag Persistent Failures
|
|
82
|
+
|
|
83
|
+
When a tool or command fails repeatedly, stop and flag it to the user for investigation. Don't paper over failures with workarounds — workarounds waste tokens and hide real problems. Describe what failed, how many times, and any obvious patterns. Let the user decide whether to investigate in a dedicated session. Inefficiencies and failures should be squashed, not hidden.
|
|
84
|
+
|
|
85
|
+
### 8. Stay On Target
|
|
86
|
+
|
|
87
|
+
Stick to the task at hand. If you discover work that's outside the current scope — a tangential bug, an adjacent improvement, a missing doc — flag it to the user but don't pursue it. If the side quest blocks or slows your progress, stop and report it. Otherwise, note it and get back on track. Scope creep mid-task burns tokens and dilutes focus.
|
|
88
|
+
|
|
89
|
+
## Lifecycle Sequence
|
|
90
|
+
|
|
91
|
+
For a complete feature, the typical skill sequence is:
|
|
92
|
+
|
|
93
|
+
1. `grill-me` → Extract what the user actually wants
|
|
94
|
+
2. `record-idea` → Refine vague ideas
|
|
95
|
+
3. `write-a-prd` → Define what we're building
|
|
96
|
+
4. `prd-to-tasks` → Break into verifiable chunks
|
|
97
|
+
5. `plan` → Orchestrate the full flow end-to-end
|
|
98
|
+
6. `ralph` → Execute all pending beads
|
|
99
|
+
7. `tdd` → Prove each slice works
|
|
100
|
+
8. `style-code` → Review before merge
|
|
101
|
+
9. `review-security` → Check for secrets
|
|
102
|
+
10. `git-workflow` → Clean commit history
|
|
103
|
+
|
|
104
|
+
Not every task needs every skill. A bug fix might only need: `tdd` → `style-code` → `review-security`.
|
|
105
|
+
|
|
106
|
+
## Red Flags
|
|
107
|
+
|
|
108
|
+
- Starting work without checking which skill applies.
|
|
109
|
+
- Skipping verification because "it looks right."
|
|
110
|
+
- Making wrong assumptions without stating them.
|
|
111
|
+
- Overcomplicating code when a simple solution exists.
|
|
112
|
+
- Modifying code orthogonal to the task scope.
|
|
113
|
+
- Going on side-quests instead of staying on target.
|
|
114
|
+
- Going on side-quests instead of staying on target.
|
|
115
|
+
- Going on side-quests instead of staying on target.
|
|
116
|
+
|
|
117
|
+
## Verification checklist
|
|
118
|
+
|
|
119
|
+
- [ ] Correct skill identified before starting work.
|
|
120
|
+
- [ ] Core operating behaviors followed throughout.
|
|
121
|
+
- [ ] Verification completed with evidence (tests, build, runtime).
|
|
122
|
+
- [ ] Scope limited to the task at hand.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// Tests for `agent-cortex install claude` runtime registration: after
|
|
2
|
+
// regenerating the claude/ subtree, the plain install registers the plugin
|
|
3
|
+
// with Claude Code by driving the `claude plugin` CLI (marketplace add →
|
|
4
|
+
// install → marketplace update → plugin update). The real CLI is a genuine
|
|
5
|
+
// external system, so these tests drive a fake `claudeBin` script that records
|
|
6
|
+
// every invocation; the real repo's marketplace.json supplies the manifest.
|
|
7
|
+
|
|
8
|
+
import { describe, it } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import {
|
|
12
|
+
mkdtempSync,
|
|
13
|
+
mkdirSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
rmSync,
|
|
16
|
+
readFileSync,
|
|
17
|
+
chmodSync,
|
|
18
|
+
existsSync,
|
|
19
|
+
} from "node:fs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
import { registerClaude } from "../bin/installers/claude.mjs";
|
|
24
|
+
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const ROOT = join(__dirname, "..");
|
|
27
|
+
const CLI_PATH = join(ROOT, "bin", "agent-cortex.mjs");
|
|
28
|
+
|
|
29
|
+
function runCli(args) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
execFile(process.execPath, [CLI_PATH, ...args], (error, stdout, stderr) => {
|
|
32
|
+
resolve({ exitCode: error ? error.code ?? 1 : 0, stdout, stderr });
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Fake claude CLI ─────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A fake `claude` binary recording every argv line to `log` (one line per
|
|
41
|
+
* invocation, space-joined). Set `failWhen` to a substring: any invocation
|
|
42
|
+
* whose argv contains it exits 1. Exits 0 otherwise.
|
|
43
|
+
*/
|
|
44
|
+
function makeFakeClaude(dir, { failWhen = null } = {}) {
|
|
45
|
+
const bin = join(dir, "claude");
|
|
46
|
+
const log = join(dir, "calls.log");
|
|
47
|
+
const script = [
|
|
48
|
+
"#!/bin/sh",
|
|
49
|
+
`echo "$*" >> "${log}"`,
|
|
50
|
+
...(failWhen
|
|
51
|
+
? [`if printf '%s' "$*" | grep -q -- "${failWhen}"; then exit 1; fi`]
|
|
52
|
+
: []),
|
|
53
|
+
"exit 0",
|
|
54
|
+
].join("\n");
|
|
55
|
+
writeFileSync(bin, `${script}\n`);
|
|
56
|
+
chmodSync(bin, 0o755);
|
|
57
|
+
return { bin, log };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function makeTmp() {
|
|
61
|
+
return mkdtempSync(join(tmpdir(), "agent-cortex-claude-installer-"));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A minimal marketplace tree (as seen in the real repo) at a scratch path. */
|
|
65
|
+
function makeMarketplaceRoot({ name = "jaybeeuu", plugin = "agent-cortex" } = {}) {
|
|
66
|
+
const root = mkdtempSync(join(tmpdir(), "agent-cortex-marketplace-"));
|
|
67
|
+
mkdirSync(join(root, ".claude-plugin"), { recursive: true });
|
|
68
|
+
writeFileSync(
|
|
69
|
+
join(root, ".claude-plugin", "marketplace.json"),
|
|
70
|
+
JSON.stringify({
|
|
71
|
+
name,
|
|
72
|
+
owner: { name: "tester" },
|
|
73
|
+
plugins: [{ name: plugin, source: "./claude", description: "test plugin" }],
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
return root;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── registerClaude ──────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
describe("registerClaude", () => {
|
|
82
|
+
it("drives claude plugin marketplace add → install → marketplace update → plugin update", () => {
|
|
83
|
+
const tmp = makeTmp();
|
|
84
|
+
const root = makeMarketplaceRoot();
|
|
85
|
+
const fake = makeFakeClaude(tmp);
|
|
86
|
+
try {
|
|
87
|
+
const result = registerClaude({ root, claudeBin: fake.bin });
|
|
88
|
+
assert.equal(result.marketplace, "jaybeeuu");
|
|
89
|
+
assert.equal(result.plugin, "agent-cortex");
|
|
90
|
+
assert.equal(result.dryRun, false);
|
|
91
|
+
|
|
92
|
+
const calls = readFileSync(fake.log, "utf-8").trim().split("\n");
|
|
93
|
+
// The availability probe (`plugin --help`) is a spawn, but the registration
|
|
94
|
+
// surface is the four commands below — assert those, in order.
|
|
95
|
+
const registration = calls.filter((c) => !c.startsWith("plugin --help"));
|
|
96
|
+
assert.deepEqual(registration, [
|
|
97
|
+
`plugin marketplace add ${root}`,
|
|
98
|
+
"plugin install agent-cortex@jaybeeuu -y",
|
|
99
|
+
"plugin marketplace update jaybeeuu",
|
|
100
|
+
"plugin update agent-cortex -y",
|
|
101
|
+
]);
|
|
102
|
+
} finally {
|
|
103
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
104
|
+
rmSync(root, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("derives marketplace and plugin names from the manifest", () => {
|
|
109
|
+
const tmp = makeTmp();
|
|
110
|
+
const root = makeMarketplaceRoot({ name: "scratchmkt", plugin: "scratchplug" });
|
|
111
|
+
const fake = makeFakeClaude(tmp);
|
|
112
|
+
try {
|
|
113
|
+
const result = registerClaude({ root, claudeBin: fake.bin });
|
|
114
|
+
assert.equal(result.marketplace, "scratchmkt");
|
|
115
|
+
assert.equal(result.plugin, "scratchplug");
|
|
116
|
+
const calls = readFileSync(fake.log, "utf-8").trim().split("\n");
|
|
117
|
+
const registration = calls.filter((c) => !c.startsWith("plugin --help"));
|
|
118
|
+
assert.ok(registration.some((c) => c === "plugin install scratchplug@scratchmkt -y"));
|
|
119
|
+
assert.ok(registration.some((c) => c === "plugin update scratchplug -y"));
|
|
120
|
+
} finally {
|
|
121
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
122
|
+
rmSync(root, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("--dry-run plans the commands without spawning claude at all", () => {
|
|
127
|
+
const tmp = makeTmp();
|
|
128
|
+
const root = makeMarketplaceRoot();
|
|
129
|
+
const fake = makeFakeClaude(tmp);
|
|
130
|
+
try {
|
|
131
|
+
const result = registerClaude({ root, claudeBin: fake.bin, dryRun: true });
|
|
132
|
+
assert.equal(result.dryRun, true);
|
|
133
|
+
assert.deepEqual(
|
|
134
|
+
result.commands,
|
|
135
|
+
[
|
|
136
|
+
["plugin", "marketplace", "add", root],
|
|
137
|
+
["plugin", "install", "agent-cortex@jaybeeuu", "-y"],
|
|
138
|
+
["plugin", "marketplace", "update", "jaybeeuu"],
|
|
139
|
+
["plugin", "update", "agent-cortex", "-y"],
|
|
140
|
+
],
|
|
141
|
+
);
|
|
142
|
+
// No registration spawns happened at all in dry-run mode.
|
|
143
|
+
assert.equal(existsSync(fake.log), false);
|
|
144
|
+
} finally {
|
|
145
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
146
|
+
rmSync(root, { recursive: true, force: true });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("defaults the claude binary to `claude` from PATH (dry-run only — no spawn)", () => {
|
|
151
|
+
const root = makeMarketplaceRoot();
|
|
152
|
+
try {
|
|
153
|
+
const result = registerClaude({ root, dryRun: true });
|
|
154
|
+
assert.equal(result.commands.length, 4);
|
|
155
|
+
assert.ok(result.commands[0][0] === "plugin");
|
|
156
|
+
} finally {
|
|
157
|
+
rmSync(root, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("fails with upgrade guidance when the claude binary lacks the plugin subcommand", () => {
|
|
162
|
+
const tmp = makeTmp();
|
|
163
|
+
const root = makeMarketplaceRoot();
|
|
164
|
+
const fake = makeFakeClaude(tmp, { failWhen: "plugin --help" });
|
|
165
|
+
try {
|
|
166
|
+
assert.throws(
|
|
167
|
+
() => registerClaude({ root, claudeBin: fake.bin }),
|
|
168
|
+
/plugin.*subcommand.*update/i,
|
|
169
|
+
);
|
|
170
|
+
} finally {
|
|
171
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
172
|
+
rmSync(root, { recursive: true, force: true });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("fails, naming the failing command, when a claude plugin command errors", () => {
|
|
177
|
+
const tmp = makeTmp();
|
|
178
|
+
const root = makeMarketplaceRoot();
|
|
179
|
+
const fake = makeFakeClaude(tmp, { failWhen: "plugin install" });
|
|
180
|
+
try {
|
|
181
|
+
assert.throws(
|
|
182
|
+
() => registerClaude({ root, claudeBin: fake.bin }),
|
|
183
|
+
/claude plugin install agent-cortex@jaybeeuu -y/,
|
|
184
|
+
);
|
|
185
|
+
} finally {
|
|
186
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
187
|
+
rmSync(root, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("fails when the checkout has no marketplace manifest", () => {
|
|
192
|
+
const tmp = makeTmp();
|
|
193
|
+
const root = mkdtempSync(join(tmpdir(), "agent-cortex-no-market-"));
|
|
194
|
+
const fake = makeFakeClaude(tmp);
|
|
195
|
+
try {
|
|
196
|
+
assert.throws(
|
|
197
|
+
() => registerClaude({ root, claudeBin: fake.bin }),
|
|
198
|
+
/marketplace\.json/,
|
|
199
|
+
);
|
|
200
|
+
// The failed manifest read happens before any claude invocation.
|
|
201
|
+
assert.equal(existsSync(fake.log), false);
|
|
202
|
+
} finally {
|
|
203
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
204
|
+
rmSync(root, { recursive: true, force: true });
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("works against the real repo checkout and its marketplace manifest", () => {
|
|
209
|
+
const tmp = makeTmp();
|
|
210
|
+
const fake = makeFakeClaude(tmp);
|
|
211
|
+
try {
|
|
212
|
+
const result = registerClaude({ root: ROOT, claudeBin: fake.bin });
|
|
213
|
+
assert.equal(result.marketplace, "jaybeeuu");
|
|
214
|
+
assert.equal(result.plugin, "agent-cortex");
|
|
215
|
+
const calls = readFileSync(fake.log, "utf-8").trim().split("\n");
|
|
216
|
+
const registration = calls.filter((c) => !c.startsWith("plugin --help"));
|
|
217
|
+
assert.equal(registration.length, 4);
|
|
218
|
+
} finally {
|
|
219
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// ─── CLI wiring ───────────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
describe("install claude CLI: registration", () => {
|
|
227
|
+
it("--dry-run without --output prints the registration plan and writes nothing", async () => {
|
|
228
|
+
const { exitCode, stdout } = await runCli(["install", "claude", "--dry-run"]);
|
|
229
|
+
assert.equal(exitCode, 0);
|
|
230
|
+
assert.ok(stdout.includes("would run: claude plugin marketplace add"));
|
|
231
|
+
assert.ok(stdout.includes("claude plugin install agent-cortex@jaybeeuu"));
|
|
232
|
+
assert.ok(stdout.includes("claude plugin marketplace update jaybeeuu"));
|
|
233
|
+
assert.ok(stdout.includes("claude plugin update agent-cortex"));
|
|
234
|
+
assert.ok(/dry-run/i.test(stdout));
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("--output <dir> generates only — no registration plan is printed", async () => {
|
|
238
|
+
const out = mkdtempSync(join(tmpdir(), "agent-cortex-cli-out-"));
|
|
239
|
+
try {
|
|
240
|
+
const { exitCode, stdout } = await runCli(["install", "claude", "--output", out]);
|
|
241
|
+
assert.equal(exitCode, 0);
|
|
242
|
+
assert.doesNotMatch(stdout, /claude plugin/);
|
|
243
|
+
assert.ok(stdout.includes("Generated"));
|
|
244
|
+
} finally {
|
|
245
|
+
rmSync(out, { recursive: true, force: true });
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
});
|
package/test/cli.test.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { describe, it } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
4
6
|
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { dirname, join } from "node:path";
|
|
6
8
|
import { parseArgs, buildHelpText, validateHarness } from "../lib/cli.mjs";
|
|
@@ -56,6 +58,84 @@ describe("parseArgs", () => {
|
|
|
56
58
|
assert.deepStrictEqual(result, { command: "install", harness: null });
|
|
57
59
|
});
|
|
58
60
|
|
|
61
|
+
it("returns output for install claude --output <dir>", () => {
|
|
62
|
+
const result = parseArgs(["install", "claude", "--output", "/tmp/x"]);
|
|
63
|
+
assert.deepStrictEqual(result, { command: "install", harness: "claude", output: "/tmp/x" });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns output for install claude --output=<dir>", () => {
|
|
67
|
+
const result = parseArgs(["install", "claude", "--output=/tmp/x"]);
|
|
68
|
+
assert.deepStrictEqual(result, { command: "install", harness: "claude", output: "/tmp/x" });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns dryRun for install claude --dry-run", () => {
|
|
72
|
+
const result = parseArgs(["install", "claude", "--dry-run"]);
|
|
73
|
+
assert.deepStrictEqual(result, { command: "install", harness: "claude", dryRun: true });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("combines --dry-run with --output", () => {
|
|
77
|
+
const result = parseArgs(["install", "claude", "--output", "/tmp/x", "--dry-run"]);
|
|
78
|
+
assert.deepStrictEqual(result, { command: "install", harness: "claude", output: "/tmp/x", dryRun: true });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("reports an unknown option for claude", () => {
|
|
82
|
+
const result = parseArgs(["install", "claude", "--bogus"]);
|
|
83
|
+
assert.equal(result.command, "install");
|
|
84
|
+
assert.equal(result.harness, "claude");
|
|
85
|
+
assert.ok(result.optionError.includes("--bogus"));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("reports a missing value for --output for claude", () => {
|
|
89
|
+
const result = parseArgs(["install", "claude", "--output"]);
|
|
90
|
+
assert.equal(result.command, "install");
|
|
91
|
+
assert.equal(result.harness, "claude");
|
|
92
|
+
assert.ok(result.optionError.includes("--output"));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("parses --dry-run for install", () => {
|
|
96
|
+
const result = parseArgs(["install", "pi", "--dry-run"]);
|
|
97
|
+
assert.deepStrictEqual(result, { command: "install", harness: "pi", dryRun: true });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("parses --output <dir> for install", () => {
|
|
101
|
+
const result = parseArgs(["install", "pi", "--output", "/tmp/out"]);
|
|
102
|
+
assert.deepStrictEqual(result, { command: "install", harness: "pi", output: "/tmp/out" });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("parses --plugin-root <dir> for install", () => {
|
|
106
|
+
const result = parseArgs(["install", "pi", "--plugin-root", "/tmp/plugin"]);
|
|
107
|
+
assert.deepStrictEqual(result, { command: "install", harness: "pi", pluginRoot: "/tmp/plugin" });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("combines multiple install options", () => {
|
|
111
|
+
const result = parseArgs(["install", "pi", "--dry-run", "--output", "/tmp/out", "--plugin-root", "/tmp/plugin"]);
|
|
112
|
+
assert.deepStrictEqual(result, {
|
|
113
|
+
command: "install",
|
|
114
|
+
harness: "pi",
|
|
115
|
+
dryRun: true,
|
|
116
|
+
output: "/tmp/out",
|
|
117
|
+
pluginRoot: "/tmp/plugin",
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("reports a missing value for --output", () => {
|
|
122
|
+
const result = parseArgs(["install", "pi", "--output"]);
|
|
123
|
+
assert.equal(result.command, "install");
|
|
124
|
+
assert.equal(result.harness, "pi");
|
|
125
|
+
assert.ok(result.optionError.includes("--output"));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("reports an unknown option", () => {
|
|
129
|
+
const result = parseArgs(["install", "pi", "--bogus"]);
|
|
130
|
+
assert.equal(result.command, "install");
|
|
131
|
+
assert.equal(result.harness, "pi");
|
|
132
|
+
assert.ok(result.optionError.includes("--bogus"));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("keeps the old shape when no options are given", () => {
|
|
136
|
+
assert.deepStrictEqual(parseArgs(["install", "pi"]), { command: "install", harness: "pi" });
|
|
137
|
+
});
|
|
138
|
+
|
|
59
139
|
it("returns { command: 'unknown', name: 'foo' } for unknown command", () => {
|
|
60
140
|
const result = parseArgs(["foo"]);
|
|
61
141
|
assert.deepStrictEqual(result, { command: "unknown", name: "foo" });
|
|
@@ -117,8 +197,9 @@ describe("CLI integration", () => {
|
|
|
117
197
|
assert.ok(stdout.includes("copilot"));
|
|
118
198
|
});
|
|
119
199
|
|
|
120
|
-
it("exits 0 for install claude", async () => {
|
|
121
|
-
const
|
|
200
|
+
it("exits 0 for install claude (into a temp output dir)", async () => {
|
|
201
|
+
const dir = mkdtempSync(join(tmpdir(), "agent-cortex-cli-"));
|
|
202
|
+
const { exitCode, stdout } = await runCli(["install", "claude", "--output", dir]);
|
|
122
203
|
assert.equal(exitCode, 0);
|
|
123
204
|
assert.ok(stdout.includes("claude"));
|
|
124
205
|
});
|
|
@@ -135,6 +216,38 @@ describe("CLI integration", () => {
|
|
|
135
216
|
assert.ok(stderr.includes("Missing harness"));
|
|
136
217
|
});
|
|
137
218
|
|
|
219
|
+
it("exits 1 and prints error for install with an unknown option", async () => {
|
|
220
|
+
const { exitCode, stderr } = await runCli(["install", "pi", "--bogus"]);
|
|
221
|
+
assert.equal(exitCode, 1);
|
|
222
|
+
assert.ok(stderr.includes("--bogus"));
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("exits 1 and prints error for install with a missing option value", async () => {
|
|
226
|
+
const { exitCode, stderr } = await runCli(["install", "pi", "--output"]);
|
|
227
|
+
assert.equal(exitCode, 1);
|
|
228
|
+
assert.ok(stderr.includes("--output"));
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("exits 0 for install pi --dry-run and writes nothing", async () => {
|
|
232
|
+
const { exitCode, stdout } = await runCli(["install", "pi", "--dry-run"]);
|
|
233
|
+
assert.equal(exitCode, 0);
|
|
234
|
+
assert.ok(stdout.includes("ralph.agent.md"));
|
|
235
|
+
assert.ok(stdout.includes("dry-run"));
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("exits 0 for install pi --output <tmp> and writes agent files", async () => {
|
|
239
|
+
const outDir = mkdtempSync(join(tmpdir(), "cli-pi-install-"));
|
|
240
|
+
try {
|
|
241
|
+
const { exitCode, stdout } = await runCli(["install", "pi", "--output", outDir]);
|
|
242
|
+
assert.equal(exitCode, 0);
|
|
243
|
+
assert.ok(stdout.includes("ralph.agent.md"));
|
|
244
|
+
const ralph = readFileSync(join(outDir, "agents", "ralph.agent.md"), "utf-8");
|
|
245
|
+
assert.ok(ralph.includes("agent-cortex:ralph"));
|
|
246
|
+
} finally {
|
|
247
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
138
251
|
it("exits 1 and prints error for install with unknown harness", async () => {
|
|
139
252
|
const { exitCode, stderr } = await runCli(["install", "docker"]);
|
|
140
253
|
assert.equal(exitCode, 1);
|