@cardor/agent-harness-kit 1.6.2 → 1.6.4
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 +44 -36
- package/dist/agent-templates/builder.md +1 -1
- package/dist/agent-templates/consultant.md +77 -0
- package/dist/agent-templates/lead.md +18 -1
- package/dist/agent-templates/reviewer.md +2 -0
- package/dist/cli.js +104 -19
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ npx ahk init
|
|
|
46
46
|
- [`ahk export`](#ahk-export)
|
|
47
47
|
- [Files created by `ahk init`](#files-created-by-ahk-init)
|
|
48
48
|
- [What each file does](#what-each-file-does)
|
|
49
|
+
- [Tasks schema](#tasks-schema)
|
|
49
50
|
- [What you can customize](#what-you-can-customize)
|
|
50
51
|
- [`agent-harness-kit.config.ts`](#agent-harness-kitconfigts)
|
|
51
52
|
- [`health.sh`](#healthsh)
|
|
@@ -53,6 +54,7 @@ npx ahk init
|
|
|
53
54
|
- [`.harness/feature_list.json`](#harnessfeature_listjson)
|
|
54
55
|
- [MCP tools (for agents)](#mcp-tools-for-agents)
|
|
55
56
|
- [Agent roles](#agent-roles)
|
|
57
|
+
- [MCP tool permissions by role](#mcp-tool-permissions-by-role)
|
|
56
58
|
- [What to commit](#what-to-commit)
|
|
57
59
|
- [Runtime compatibility](#runtime-compatibility)
|
|
58
60
|
- [Contributing \& local development](#contributing--local-development)
|
|
@@ -577,56 +579,62 @@ Good acceptance criteria make the difference — the reviewer agent uses them to
|
|
|
577
579
|
|
|
578
580
|
The harness exposes these tools via MCP. Agents use them instead of reading files directly.
|
|
579
581
|
|
|
580
|
-
| Tool | Parameters | Description
|
|
581
|
-
| ------------------------- | ----------------------------------------------- |
|
|
582
|
-
| `tasks.get` | `status?` | List tasks, optionally filtered by `pending \| in_progress \| done \| blocked`
|
|
583
|
-
| `tasks.claim` | `id, agent` | Atomically claim a pending task. Returns `task_already_claimed` if another agent got it first
|
|
584
|
-
| `tasks.update` | `id, status` | Change task status
|
|
585
|
-
| `tasks.add` | `title, slug?, description?, acceptance?` | Create a new task directly from MCP (agents can queue work on the fly)
|
|
586
|
-
| `tasks.acceptance.update` | `criterionId` | Mark an acceptance criterion as met. Criterion IDs come from `tasks.acceptance_get`
|
|
587
|
-
| `actions.start` | `taskId, agent` | Start a new action, returns `actionId`
|
|
588
|
-
| `actions.write` | `actionId, sectionType, content` | Record a text section: `result \| tools_used \| blockers \| next_steps`. Does **not** populate the Files dashboard — use `actions.record_file` for that
|
|
589
|
-
| `actions.complete` | `actionId, summary` | Close an action with a one-line summary
|
|
590
|
-
| `actions.get` | `taskId` | Full action history for a task (all agents, all sections)
|
|
591
|
-
| `actions.record_file` | `actionId, filePath, operation, notes?` | Register a file touch. The **only** way to populate the Files dashboard. `operation`: `read \| created \| modified \| deleted`
|
|
592
|
-
| `actions.record_tool` | `actionId, toolName, argsJson?, resultSummary?` | Register a tool call. The **only** way to populate the Tools dashboard
|
|
593
|
-
| `docs.search` | `query` | Search the `docsPath` folder for content matching the query
|
|
594
|
-
| `tasks.acceptance_get` | `taskId`
|
|
582
|
+
| Tool | Parameters | Description |
|
|
583
|
+
| ------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
584
|
+
| `tasks.get` | `status?` | List tasks, optionally filtered by `pending \| in_progress \| done \| blocked` |
|
|
585
|
+
| `tasks.claim` | `id, agent` | Atomically claim a pending task. Returns `task_already_claimed` if another agent got it first |
|
|
586
|
+
| `tasks.update` | `id, status` | Change task status |
|
|
587
|
+
| `tasks.add` | `title, slug?, description?, acceptance?` | Create a new task directly from MCP (agents can queue work on the fly) |
|
|
588
|
+
| `tasks.acceptance.update` | `criterionId` | Mark an acceptance criterion as met. Criterion IDs come from `tasks.acceptance_get` |
|
|
589
|
+
| `actions.start` | `taskId, agent` | Start a new action, returns `actionId` |
|
|
590
|
+
| `actions.write` | `actionId, sectionType, content` | Record a text section: `result \| tools_used \| blockers \| next_steps`. Does **not** populate the Files dashboard — use `actions.record_file` for that |
|
|
591
|
+
| `actions.complete` | `actionId, summary` | Close an action with a one-line summary |
|
|
592
|
+
| `actions.get` | `taskId` | Full action history for a task (all agents, all sections) |
|
|
593
|
+
| `actions.record_file` | `actionId, filePath, operation, notes?` | Register a file touch. The **only** way to populate the Files dashboard. `operation`: `read \| created \| modified \| deleted` |
|
|
594
|
+
| `actions.record_tool` | `actionId, toolName, argsJson?, resultSummary?` | Register a tool call. The **only** way to populate the Tools dashboard |
|
|
595
|
+
| `docs.search` | `query` | Search the `docsPath` folder for content matching the query |
|
|
596
|
+
| `tasks.acceptance_get` | `taskId` | Returns all acceptance criteria for a task with their `id`, `task_id`, `criterion` text, and `met` status. Use the returned `id` values with `tasks.acceptance.update` |
|
|
597
|
+
| `deps.snapshot` | _(none)_ | Snapshot current `package.json` dependencies to `.harness/deps-lock.json` |
|
|
598
|
+
| `deps.check` | _(none)_ | Compare current `package.json` against `.harness/deps-lock.json`. Returns `{ significant, added, removed, majorBumps, advisory }` |
|
|
595
599
|
|
|
596
600
|
---
|
|
597
601
|
|
|
598
602
|
## Agent roles
|
|
599
603
|
|
|
600
|
-
| Role
|
|
601
|
-
|
|
|
602
|
-
| **lead**
|
|
603
|
-
| **explorer**
|
|
604
|
-
| **
|
|
605
|
-
| **
|
|
604
|
+
| Role | Responsibility |
|
|
605
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
606
|
+
| **lead** | Decomposes the task into a plan, assigns sub-agents. Does not write code or read source files. |
|
|
607
|
+
| **explorer** | Reads and maps the codebase. Never writes files. Records every file read. |
|
|
608
|
+
| **consultant** | Provides structured technical advisory after explorer. Runs conditionally. Never writes code. Writes advisory to harness via actions.write. |
|
|
609
|
+
| **builder** | Implements the plan. Only writes to `writablePaths`. Records every file modified. |
|
|
610
|
+
| **reviewer** | Verifies all acceptance criteria are met. Approves or blocks. Runs health check before approving. |
|
|
606
611
|
|
|
607
612
|
### MCP tool permissions by role
|
|
608
613
|
|
|
609
614
|
Each agent role has a scoped set of MCP tools enforced through the agent definition files.
|
|
610
615
|
|
|
611
|
-
| Tool
|
|
612
|
-
|
|
613
|
-
| `tasks.get`
|
|
614
|
-
| `tasks.claim`
|
|
615
|
-
| `tasks.add`
|
|
616
|
-
| `tasks.update`
|
|
617
|
-
| `tasks.edit`
|
|
618
|
-
| `tasks.archive` / `unarchive` |
|
|
619
|
-
| `tasks.acceptance_get`
|
|
620
|
-
| `tasks.acceptance.update`
|
|
621
|
-
| `actions.*` (all 6)
|
|
622
|
-
| `docs.search`
|
|
623
|
-
| `permissions.check`
|
|
616
|
+
| Tool | lead | explorer | consultant | builder | reviewer |
|
|
617
|
+
| ----------------------------- | :--: | :------: | :--------: | :-----: | :------: |
|
|
618
|
+
| `tasks.get` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
619
|
+
| `tasks.claim` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
620
|
+
| `tasks.add` | ✅ | ❌ | ❌ | ✅ | ✅ |
|
|
621
|
+
| `tasks.update` | ✅ | ❌ | ❌ | ✅ | ✅ |
|
|
622
|
+
| `tasks.edit` | ✅ | ❌ | ❌ | ✅ | ✅ |
|
|
623
|
+
| `tasks.archive` / `unarchive` | ✅ | ❌ | ❌ | ✅ | ✅ |
|
|
624
|
+
| `tasks.acceptance_get` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
625
|
+
| `tasks.acceptance.update` | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
626
|
+
| `actions.*` (all 6) | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
627
|
+
| `docs.search` | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
628
|
+
| `permissions.check` | ✅ | ✅ | ❌ | ✅ | ✅ |
|
|
629
|
+
| `deps.snapshot` | ❌ | ❌ | ✅ | ❌ | ❌ |
|
|
630
|
+
| `deps.check` | ❌ | ❌ | ✅ | ❌ | ❌ |
|
|
624
631
|
|
|
625
632
|
**explorer** is read-only for task state — can query but cannot mutate status or mark criteria.
|
|
626
633
|
**reviewer** is the only role that can mark acceptance criteria as met (`tasks.acceptance.update`).
|
|
627
|
-
**lead** and **builder** have identical access, both excluding `tasks.acceptance.update`.
|
|
634
|
+
**lead** and **builder** have identical access, both excluding `tasks.acceptance.update`.
|
|
635
|
+
**consultant** is advisory-only — reads code, writes to harness, and can call deps tools. Never modifies the codebase.
|
|
628
636
|
|
|
629
|
-
`permissions.check` compares each `.claude/agents/*.md` tool list against the canonical constants in the package. Returns `{ in_sync: bool, agents: { lead, explorer, builder, reviewer } }` with per-agent `missing` and `extra` arrays. Run `ahk build --sync` to fix any drift.
|
|
637
|
+
`permissions.check` compares each `.claude/agents/*.md` tool list against the canonical constants in the package. Returns `{ in_sync: bool, agents: { lead, explorer, consultant, builder, reviewer } }` with per-agent `missing` and `extra` arrays. Run `ahk build --sync` to fix any drift.
|
|
630
638
|
|
|
631
639
|
---
|
|
632
640
|
|
|
@@ -75,7 +75,7 @@ If you touched 5 files and made 12 tool calls, there must be 5 `actions.record_f
|
|
|
75
75
|
actions.get(taskId)
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Read
|
|
78
|
+
Read ALL previous actions via `actions.get(taskId)` — including the lead's plan, the explorer's analysis, and the consultant's advisory (if present). Do not rely on the lead summary alone. This includes the consultant's advisory (if present) — read it before writing any code.
|
|
79
79
|
|
|
80
80
|
### 2. Register your action
|
|
81
81
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: consultant
|
|
3
|
+
description: >
|
|
4
|
+
Technical advisor agent for {{projectName}}. Runs after the explorer and before the builder.
|
|
5
|
+
Provides structured advisory — patterns, best practices, warnings, and risks — written
|
|
6
|
+
directly to the harness so the builder can read it via actions.get. Never writes code.
|
|
7
|
+
tools:
|
|
8
|
+
- Read
|
|
9
|
+
- Bash
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Consultant Agent — {{projectName}}
|
|
13
|
+
|
|
14
|
+
You are the **consultant agent** for `{{projectName}}`. Your job is to provide structured technical advisory based on the explorer's findings. You do not write code or modify files.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## !! ABSOLUTE CONSTRAINT !!
|
|
19
|
+
|
|
20
|
+
**YOU ARE FORBIDDEN FROM MODIFYING THE CODEBASE IN ANY WAY.**
|
|
21
|
+
|
|
22
|
+
Read files. Think. Write your advisory to the harness. That is all.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Responsibilities
|
|
27
|
+
|
|
28
|
+
- Read the explorer's output via `actions.get(taskId)`
|
|
29
|
+
- Analyse the relevant code sections identified by the explorer
|
|
30
|
+
- Produce a structured advisory covering: patterns to follow, pitfalls to avoid, best practices, risks
|
|
31
|
+
- Record your advisory directly in the harness so the builder reads it without lead filtering
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Workflow
|
|
36
|
+
|
|
37
|
+
### 1. Read context
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
actions.get(taskId) → read explorer's analysis and lead's plan
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. Analyse
|
|
44
|
+
|
|
45
|
+
Read the files the explorer mapped. Focus on:
|
|
46
|
+
- Existing patterns the builder must follow for consistency
|
|
47
|
+
- Known gotchas or constraints in the affected code
|
|
48
|
+
- Any risks introduced by the proposed change (breaking changes, perf, security)
|
|
49
|
+
- Whether the task touches dependencies — if so, note any implications
|
|
50
|
+
|
|
51
|
+
### 3. Write advisory
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
actions.start(taskId, 'consultant') → save actionId
|
|
55
|
+
actions.write(actionId, 'result', '<your structured advisory>')
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Structure your advisory with clear headings:
|
|
59
|
+
- **Patterns to follow** — what existing conventions apply
|
|
60
|
+
- **Risks & warnings** — what could go wrong
|
|
61
|
+
- **Best practices** — what the builder should keep in mind
|
|
62
|
+
- **Dependency notes** — only if task touches package.json or deps
|
|
63
|
+
|
|
64
|
+
### 4. Complete
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
actions.complete(actionId, 'Advisory written — <one-line summary>')
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Hard rules
|
|
73
|
+
|
|
74
|
+
- **No file writes, no edits, no Bash that changes state.** Read only.
|
|
75
|
+
- **Do not summarize or paraphrase** the explorer's output for the builder — add new insight.
|
|
76
|
+
- **Be specific.** Vague advice like "be careful" is useless. Name the file, line, pattern.
|
|
77
|
+
- **One action per session.** Open one action, write your advisory, close it.
|
|
@@ -91,6 +91,13 @@ Then call `permissions.check` — if `in_sync: false`, inform the user before pr
|
|
|
91
91
|
> "Your agent permissions are outdated. Run `ahk build --sync` to update, or I can guide you."
|
|
92
92
|
Wait for the user to acknowledge before continuing the session.
|
|
93
93
|
|
|
94
|
+
Then run deps tracking:
|
|
95
|
+
```
|
|
96
|
+
deps.snapshot → save current dependency state (creates .harness/deps-lock.json if missing)
|
|
97
|
+
deps.check → returns diff vs. last snapshot
|
|
98
|
+
```
|
|
99
|
+
Save the `deps.check` result — you'll use it in step 7 to decide whether to invoke the consultant.
|
|
100
|
+
|
|
94
101
|
Then check session state via MCP:
|
|
95
102
|
|
|
96
103
|
```
|
|
@@ -139,6 +146,9 @@ Think through:
|
|
|
139
146
|
- What are the acceptance criteria the reviewer will check?
|
|
140
147
|
- If codebase changes are involved: does the builder need to update README or `docs/` files?
|
|
141
148
|
- Does this task touch user-facing behavior (CLI commands, MCP tools, DB schema, config, agent permissions)? If yes, add an acceptance criterion: `README.md and/or docs/ updated to reflect the change`
|
|
149
|
+
- **Always append, as the LAST acceptance criterion for every task, this mandatory criterion:**
|
|
150
|
+
> `Docs/README analysis: [describe whether docs/, README.md, or other documentation files need to reflect this change and what specifically — or explicitly state 'no update needed' with brief reasoning]`
|
|
151
|
+
The analysis is non-negotiable. The conclusion can be "no update needed" but the reasoning must be stated. The reviewer will block if this criterion is absent or if the builder's action summary is silent on docs.
|
|
142
152
|
|
|
143
153
|
Record it:
|
|
144
154
|
|
|
@@ -156,13 +166,20 @@ actions.complete(actionId, 'Plan defined — delegating to explorer')
|
|
|
156
166
|
|
|
157
167
|
### 7. Delegate in order
|
|
158
168
|
|
|
159
|
-
Invoke: **Explorer** → **Builder** → **Reviewer**
|
|
169
|
+
Invoke: **Explorer** → **Consultant** (conditional) → **Builder** → **Reviewer**
|
|
160
170
|
|
|
161
171
|
After each agent completes, read their output:
|
|
162
172
|
```
|
|
163
173
|
actions.get(taskId) → read the latest completed action and its sections
|
|
164
174
|
```
|
|
165
175
|
|
|
176
|
+
**Invoke the Consultant when ANY of these are true:**
|
|
177
|
+
- `deps.check` returned `significant: true`
|
|
178
|
+
- `.harness/deps-lock.json` did not exist before this session (first task)
|
|
179
|
+
- The task description mentions `package.json`, dependencies, or config files
|
|
180
|
+
|
|
181
|
+
**Skip the Consultant** for routine feature/bug tasks where deps are unchanged.
|
|
182
|
+
|
|
166
183
|
### 8. Handle a Reviewer block
|
|
167
184
|
|
|
168
185
|
If the reviewer blocks the task:
|
|
@@ -125,6 +125,7 @@ Then notify lead so the builder can be re-assigned.
|
|
|
125
125
|
- **Be specific when blocking.** The builder must know exactly what to fix.
|
|
126
126
|
- **Do not fix issues yourself.** Your job is to verify, not to implement.
|
|
127
127
|
- **Do not approve under time pressure.** If the work is not ready, block it.
|
|
128
|
+
- **Verify the mandatory docs/README analysis criterion.** Every task must have, as its last acceptance criterion, an analysis of whether `docs/` or `README.md` need updating. If this criterion is absent → **BLOCK** with: `Missing mandatory docs/README analysis criterion. Lead must add it before builder proceeds.` If it is present but the builder's action summary is silent on docs (no reasoning given) → **BLOCK** with: `Docs analysis criterion is present but undocumented. Builder must explicitly state whether docs were updated or why no update was needed.`
|
|
128
129
|
|
|
129
130
|
## What counts as a block
|
|
130
131
|
|
|
@@ -135,6 +136,7 @@ Then notify lead so the builder can be re-assigned.
|
|
|
135
136
|
- Files modified outside the builder's allowed paths
|
|
136
137
|
- Security issues introduced by the changes
|
|
137
138
|
- The implementation does not match the lead's plan
|
|
139
|
+
- Mandatory docs/README analysis criterion absent from the task, or present but not addressed in the builder's action summary
|
|
138
140
|
|
|
139
141
|
## Anti-patterns to avoid
|
|
140
142
|
|
package/dist/cli.js
CHANGED
|
@@ -131,12 +131,27 @@ var MCP_CLAUDE_PERMISSIONS_REVIEWER = [
|
|
|
131
131
|
"mcp__agent-harness-kit__tasks_acceptance_get",
|
|
132
132
|
"mcp__agent-harness-kit__docs_search"
|
|
133
133
|
];
|
|
134
|
+
var MCP_CLAUDE_PERMISSIONS_CONSULTANT = [
|
|
135
|
+
"mcp__agent-harness-kit__actions_start",
|
|
136
|
+
"mcp__agent-harness-kit__actions_write",
|
|
137
|
+
"mcp__agent-harness-kit__actions_complete",
|
|
138
|
+
"mcp__agent-harness-kit__actions_get",
|
|
139
|
+
"mcp__agent-harness-kit__actions_record_file",
|
|
140
|
+
"mcp__agent-harness-kit__actions_record_tool",
|
|
141
|
+
"mcp__agent-harness-kit__tasks_get",
|
|
142
|
+
"mcp__agent-harness-kit__tasks_claim",
|
|
143
|
+
"mcp__agent-harness-kit__tasks_acceptance_get",
|
|
144
|
+
"mcp__agent-harness-kit__deps_snapshot",
|
|
145
|
+
"mcp__agent-harness-kit__deps_check",
|
|
146
|
+
"mcp__agent-harness-kit__docs_search"
|
|
147
|
+
];
|
|
134
148
|
var MCP_CLAUDE_PERMISSIONS = [
|
|
135
149
|
.../* @__PURE__ */ new Set([
|
|
136
150
|
...MCP_CLAUDE_PERMISSIONS_LEAD,
|
|
137
151
|
...MCP_CLAUDE_PERMISSIONS_EXPLORER,
|
|
138
152
|
...MCP_CLAUDE_PERMISSIONS_BUILDER,
|
|
139
|
-
...MCP_CLAUDE_PERMISSIONS_REVIEWER
|
|
153
|
+
...MCP_CLAUDE_PERMISSIONS_REVIEWER,
|
|
154
|
+
...MCP_CLAUDE_PERMISSIONS_CONSULTANT
|
|
140
155
|
])
|
|
141
156
|
];
|
|
142
157
|
function mergeClaudeSettingsLocalJson(filePath) {
|
|
@@ -491,6 +506,9 @@ function agentExplorer(vars) {
|
|
|
491
506
|
function agentBuilder(vars) {
|
|
492
507
|
return loadAgentTemplate("builder", vars);
|
|
493
508
|
}
|
|
509
|
+
function agentConsultant(vars) {
|
|
510
|
+
return loadAgentTemplate("consultant", vars);
|
|
511
|
+
}
|
|
494
512
|
function agentReviewer(vars) {
|
|
495
513
|
return loadAgentTemplate("reviewer", vars);
|
|
496
514
|
}
|
|
@@ -548,10 +566,11 @@ function agentReviewerToml(vars) {
|
|
|
548
566
|
}
|
|
549
567
|
function translateFrontmatterForClaudeCode(md, agentName) {
|
|
550
568
|
const permissionsMap = {
|
|
551
|
-
lead: MCP_CLAUDE_PERMISSIONS_LEAD,
|
|
552
|
-
explorer: MCP_CLAUDE_PERMISSIONS_EXPLORER,
|
|
553
|
-
|
|
554
|
-
|
|
569
|
+
lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
|
|
570
|
+
explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
|
|
571
|
+
consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
|
|
572
|
+
builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
|
|
573
|
+
reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
|
|
555
574
|
};
|
|
556
575
|
const permissions = permissionsMap[agentName] ?? MCP_CLAUDE_PERMISSIONS;
|
|
557
576
|
const mcpLines = permissions.map((t) => ` - ${t}`).join("\n");
|
|
@@ -590,10 +609,11 @@ function slugify(title) {
|
|
|
590
609
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
591
610
|
}
|
|
592
611
|
var AGENT_TOOLS = {
|
|
593
|
-
lead: MCP_CLAUDE_PERMISSIONS_LEAD,
|
|
594
|
-
explorer: MCP_CLAUDE_PERMISSIONS_EXPLORER,
|
|
595
|
-
|
|
596
|
-
|
|
612
|
+
lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
|
|
613
|
+
explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
|
|
614
|
+
consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
|
|
615
|
+
builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
|
|
616
|
+
reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
|
|
597
617
|
};
|
|
598
618
|
async function syncAgentPermissions(cwd2) {
|
|
599
619
|
for (const [agent, tools] of Object.entries(AGENT_TOOLS)) {
|
|
@@ -645,6 +665,7 @@ No tasks in progress.
|
|
|
645
665
|
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
646
666
|
writeAgentFile(cwd2, ".claude/agents/lead.md", translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead"));
|
|
647
667
|
writeAgentFile(cwd2, ".claude/agents/explorer.md", translateFrontmatterForClaudeCode(agentExplorer({ projectName, allowedPaths }), "explorer"));
|
|
668
|
+
writeAgentFile(cwd2, ".claude/agents/consultant.md", translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant"));
|
|
648
669
|
writeAgentFile(cwd2, ".claude/agents/builder.md", translateFrontmatterForClaudeCode(agentBuilder({ projectName, writablePaths }), "builder"));
|
|
649
670
|
writeAgentFile(cwd2, ".claude/agents/reviewer.md", translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer"));
|
|
650
671
|
mergeClaudeMcpJson(join4(cwd2, ".mcp.json"), config.tools.mcp.port);
|
|
@@ -665,6 +686,7 @@ No tasks in progress.
|
|
|
665
686
|
const writablePaths = (config.agents.builder.writablePaths ?? []).join(", ");
|
|
666
687
|
writeAgentFile(cwd2, ".claude/agents/lead.md", translateFrontmatterForClaudeCode(agentLead({ projectName }), "lead"));
|
|
667
688
|
writeAgentFile(cwd2, ".claude/agents/explorer.md", translateFrontmatterForClaudeCode(agentExplorer({ projectName, allowedPaths }), "explorer"));
|
|
689
|
+
writeAgentFile(cwd2, ".claude/agents/consultant.md", translateFrontmatterForClaudeCode(agentConsultant({ projectName }), "consultant"));
|
|
668
690
|
writeAgentFile(cwd2, ".claude/agents/builder.md", translateFrontmatterForClaudeCode(agentBuilder({ projectName, writablePaths }), "builder"));
|
|
669
691
|
writeAgentFile(cwd2, ".claude/agents/reviewer.md", translateFrontmatterForClaudeCode(agentReviewer({ projectName }), "reviewer"));
|
|
670
692
|
mergeClaudeMcpJson(join4(cwd2, ".mcp.json"), config.tools.mcp.port);
|
|
@@ -2353,7 +2375,7 @@ async function runReset(cwd2, opts) {
|
|
|
2353
2375
|
}
|
|
2354
2376
|
|
|
2355
2377
|
// src/core/mcp-server.ts
|
|
2356
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync7, statSync } from "fs";
|
|
2378
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync9, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync10 } from "fs";
|
|
2357
2379
|
import { join as join15, resolve as resolve10 } from "path";
|
|
2358
2380
|
import { Server } from "@modelcontextprotocol/sdk/server";
|
|
2359
2381
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -2366,10 +2388,11 @@ import {
|
|
|
2366
2388
|
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
2367
2389
|
import { join as join14 } from "path";
|
|
2368
2390
|
var CANONICAL = {
|
|
2369
|
-
lead: MCP_CLAUDE_PERMISSIONS_LEAD,
|
|
2370
|
-
explorer: MCP_CLAUDE_PERMISSIONS_EXPLORER,
|
|
2371
|
-
|
|
2372
|
-
|
|
2391
|
+
lead: [...MCP_CLAUDE_PERMISSIONS_LEAD],
|
|
2392
|
+
explorer: [...MCP_CLAUDE_PERMISSIONS_EXPLORER],
|
|
2393
|
+
consultant: [...MCP_CLAUDE_PERMISSIONS_CONSULTANT],
|
|
2394
|
+
builder: [...MCP_CLAUDE_PERMISSIONS_BUILDER],
|
|
2395
|
+
reviewer: [...MCP_CLAUDE_PERMISSIONS_REVIEWER]
|
|
2373
2396
|
};
|
|
2374
2397
|
function parseToolsFromFrontmatter(content) {
|
|
2375
2398
|
const match = content.match(/^---\n([\s\S]*?)\n---/m);
|
|
@@ -2382,7 +2405,7 @@ function parseToolsFromFrontmatter(content) {
|
|
|
2382
2405
|
function checkPermissionsSync(cwd2) {
|
|
2383
2406
|
const agents = {};
|
|
2384
2407
|
let in_sync = true;
|
|
2385
|
-
for (const agent of ["lead", "explorer", "builder", "reviewer"]) {
|
|
2408
|
+
for (const agent of ["lead", "explorer", "consultant", "builder", "reviewer"]) {
|
|
2386
2409
|
const filePath = join14(cwd2, ".claude", "agents", `${agent}.md`);
|
|
2387
2410
|
if (!existsSync10(filePath)) {
|
|
2388
2411
|
const missing2 = CANONICAL[agent];
|
|
@@ -2631,6 +2654,16 @@ var TOOLS = [
|
|
|
2631
2654
|
name: "permissions.check",
|
|
2632
2655
|
description: "Check whether the .claude/agents/*.md tool permission lists are in sync with the current canonical permission constants. Returns per-agent diff with missing and extra tools. Call this at session start to detect outdated agent files after an ahk upgrade.",
|
|
2633
2656
|
inputSchema: { type: "object", properties: {}, required: [] }
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
name: "deps.snapshot",
|
|
2660
|
+
description: "Snapshot current package.json dependencies to .harness/deps-lock.json",
|
|
2661
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
2662
|
+
},
|
|
2663
|
+
{
|
|
2664
|
+
name: "deps.check",
|
|
2665
|
+
description: "Compare current package.json against .harness/deps-lock.json and report changes",
|
|
2666
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
2634
2667
|
}
|
|
2635
2668
|
];
|
|
2636
2669
|
async function startMcpServer(config, cwd2) {
|
|
@@ -2777,6 +2810,58 @@ async function dispatch(name, args, db, docsPath, cwd2) {
|
|
|
2777
2810
|
const result = checkPermissionsSync(cwd2);
|
|
2778
2811
|
return ok(JSON.stringify(result, null, 2));
|
|
2779
2812
|
}
|
|
2813
|
+
case "deps.snapshot": {
|
|
2814
|
+
const pkgPath2 = join15(cwd2, "package.json");
|
|
2815
|
+
if (!existsSync11(pkgPath2)) {
|
|
2816
|
+
return ok("package.json not found in project root", true);
|
|
2817
|
+
}
|
|
2818
|
+
const pkg2 = JSON.parse(readFileSync7(pkgPath2, "utf8"));
|
|
2819
|
+
const snapshot = {
|
|
2820
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2821
|
+
dependencies: pkg2.dependencies ?? {},
|
|
2822
|
+
devDependencies: pkg2.devDependencies ?? {}
|
|
2823
|
+
};
|
|
2824
|
+
const harnessDir = join15(cwd2, ".harness");
|
|
2825
|
+
mkdirSync9(harnessDir, { recursive: true });
|
|
2826
|
+
writeFileSync10(join15(harnessDir, "deps-lock.json"), JSON.stringify(snapshot, null, 2), "utf8");
|
|
2827
|
+
return ok(JSON.stringify({ message: "Snapshot saved to .harness/deps-lock.json", capturedAt: snapshot.capturedAt }));
|
|
2828
|
+
}
|
|
2829
|
+
case "deps.check": {
|
|
2830
|
+
const pkgPath2 = join15(cwd2, "package.json");
|
|
2831
|
+
const lockPath = join15(cwd2, ".harness", "deps-lock.json");
|
|
2832
|
+
if (!existsSync11(pkgPath2)) {
|
|
2833
|
+
return ok("package.json not found in project root", true);
|
|
2834
|
+
}
|
|
2835
|
+
if (!existsSync11(lockPath)) {
|
|
2836
|
+
return ok(JSON.stringify({ status: "no-snapshot", message: "No deps-lock.json found. Run deps.snapshot first to establish a baseline." }));
|
|
2837
|
+
}
|
|
2838
|
+
const pkg2 = JSON.parse(readFileSync7(pkgPath2, "utf8"));
|
|
2839
|
+
const lock = JSON.parse(readFileSync7(lockPath, "utf8"));
|
|
2840
|
+
const current = { ...pkg2.dependencies ?? {}, ...pkg2.devDependencies ?? {} };
|
|
2841
|
+
const previous = { ...lock.dependencies ?? {}, ...lock.devDependencies ?? {} };
|
|
2842
|
+
const added = [];
|
|
2843
|
+
const removed = [];
|
|
2844
|
+
const majorBumps = [];
|
|
2845
|
+
for (const [name2, version] of Object.entries(current)) {
|
|
2846
|
+
if (!(name2 in previous)) {
|
|
2847
|
+
added.push(`${name2}@${version}`);
|
|
2848
|
+
} else {
|
|
2849
|
+
const prevMajor = parseInt(previous[name2].replace(/^[\^~>=v]/, "").split(".")[0] ?? "0", 10);
|
|
2850
|
+
const curMajor = parseInt(version.replace(/^[\^~>=v]/, "").split(".")[0] ?? "0", 10);
|
|
2851
|
+
if (!isNaN(prevMajor) && !isNaN(curMajor) && curMajor > prevMajor) {
|
|
2852
|
+
majorBumps.push({ name: name2, from: previous[name2], to: version });
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
for (const depName of Object.keys(previous)) {
|
|
2857
|
+
if (!(depName in current)) {
|
|
2858
|
+
removed.push(depName);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
const significant = added.length > 0 || removed.length > 0 || majorBumps.length > 0;
|
|
2862
|
+
const advisory = significant ? "Significant dependency changes detected. Consider running `pnpx autoskills` (or `npx autoskills` if pnpm is unavailable) to refresh agent skills. Clearing stale skills before re-running is recommended." : "No significant dependency changes detected.";
|
|
2863
|
+
return ok(JSON.stringify({ significant, added, removed, majorBumps, advisory, snapshotDate: lock.capturedAt }));
|
|
2864
|
+
}
|
|
2780
2865
|
default:
|
|
2781
2866
|
return ok(`Unknown tool: ${name}`, true);
|
|
2782
2867
|
}
|
|
@@ -2934,7 +3019,7 @@ async function runStatus(cwd2, opts) {
|
|
|
2934
3019
|
}
|
|
2935
3020
|
|
|
2936
3021
|
// src/commands/sync.ts
|
|
2937
|
-
import { existsSync as
|
|
3022
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
2938
3023
|
import { join as join16, resolve as resolve11 } from "path";
|
|
2939
3024
|
import pc10 from "picocolors";
|
|
2940
3025
|
async function runSync(cwd2, opts) {
|
|
@@ -2954,7 +3039,7 @@ async function runSync(cwd2, opts) {
|
|
|
2954
3039
|
}
|
|
2955
3040
|
}
|
|
2956
3041
|
async function syncIn(featureListPath, db, dryRun) {
|
|
2957
|
-
if (!
|
|
3042
|
+
if (!existsSync12(featureListPath)) {
|
|
2958
3043
|
console.log(pc10.dim(`feature_list.json not found at ${featureListPath} \u2014 skipping in-sync`));
|
|
2959
3044
|
return;
|
|
2960
3045
|
}
|
|
@@ -3045,14 +3130,14 @@ async function runTaskAdd(cwd2) {
|
|
|
3045
3130
|
|
|
3046
3131
|
// src/commands/task/done.ts
|
|
3047
3132
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3048
|
-
import { existsSync as
|
|
3133
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3049
3134
|
import { resolve as resolve12 } from "path";
|
|
3050
3135
|
import pc12 from "picocolors";
|
|
3051
3136
|
async function runTaskDone(cwd2, idOrSlug) {
|
|
3052
3137
|
const config = await loadConfig(cwd2);
|
|
3053
3138
|
if (config.health.required) {
|
|
3054
3139
|
const scriptPath = resolve12(cwd2, config.health.scriptPath);
|
|
3055
|
-
if (
|
|
3140
|
+
if (existsSync13(scriptPath)) {
|
|
3056
3141
|
const result = spawnSync2("bash", [scriptPath], { cwd: cwd2, stdio: "pipe", encoding: "utf8" });
|
|
3057
3142
|
if (result.status !== 0) {
|
|
3058
3143
|
console.error(pc12.red("\u2717 Health check failed \u2014 cannot mark task as done."));
|